diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index 42b2f16f6..6b20d3e22 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -50,7 +50,6 @@ - diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings index 705d6dc5c..006fb1134 100644 --- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings +++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings @@ -1,4 +1,5 @@  + True True True True @@ -42,6 +43,7 @@ True True True + True True True True diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index 5de642452..36f32cada 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -12,7 +12,7 @@ namespace Artemis.Core /// /// The full path to the Artemis application folder /// - public static readonly string ApplicationFolder = Path.GetDirectoryName(typeof(Constants).Assembly.Location); + public static readonly string ApplicationFolder = Path.GetDirectoryName(typeof(Constants).Assembly.Location)!; /// /// The full path to the Artemis executable @@ -34,11 +34,16 @@ namespace Artemis.Core /// public static readonly PluginInfo CorePluginInfo = new PluginInfo { - Guid = Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff"), Name = "Artemis Core", Enabled = true + Guid = Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff"), Name = "Artemis Core" }; - internal static readonly CorePlugin CorePlugin = new CorePlugin {PluginInfo = CorePluginInfo}; - internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new EffectPlaceholderPlugin {PluginInfo = CorePluginInfo}; + /// + /// The plugin used by core components of Artemis + /// + public static readonly Plugin CorePlugin = new Plugin(CorePluginInfo, new DirectoryInfo(ApplicationFolder)); + + internal static readonly CorePluginFeature CorePluginFeature = new CorePluginFeature {Plugin = CorePlugin}; + internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new EffectPlaceholderPlugin {Plugin = CorePlugin}; /// /// A read-only collection containing all primitive numeric types diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/EqualsConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/EqualsConditionOperator.cs index dc66e12a3..789f86d63 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/EqualsConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/EqualsConditionOperator.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class EqualsConditionOperator : ConditionOperator { diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/GreaterThanConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/GreaterThanConditionOperator.cs index de5e61ea3..f9a898107 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/GreaterThanConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/GreaterThanConditionOperator.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class GreaterThanConditionOperator : ConditionOperator { diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/GreaterThanOrEqualConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/GreaterThanOrEqualConditionOperator.cs index a62b19fda..f0a1bf2b9 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/GreaterThanOrEqualConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/GreaterThanOrEqualConditionOperator.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class GreaterThanOrEqualConditionOperator : ConditionOperator { diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/LessThanConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/LessThanConditionOperator.cs index bad93babc..720cd7c2c 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/LessThanConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/LessThanConditionOperator.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class LessThanConditionOperator : ConditionOperator { diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/LessThanOrEqualConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/LessThanOrEqualConditionOperator.cs index 98cb11fca..925a4afaa 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/LessThanOrEqualConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/LessThanOrEqualConditionOperator.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class LessThanOrEqualConditionOperator : ConditionOperator { diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NotEqualConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/NotEqualConditionOperator.cs index 502888ee0..4eeb8441a 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NotEqualConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/NotEqualConditionOperator.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class NotEqualConditionOperator : ConditionOperator { diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NotNullConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/NotNullConditionOperator.cs index faf7e7054..0d8599df7 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NotNullConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/NotNullConditionOperator.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class NotNullConditionOperator : ConditionOperator { diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NullConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/NullConditionOperator.cs index 27a29508f..46302a5ff 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NullConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/NullConditionOperator.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class NullConditionOperator : ConditionOperator { diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NumberEqualsConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/NumberEqualsConditionOperator.cs index c0f303553..d32104262 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NumberEqualsConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/NumberEqualsConditionOperator.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class NumberEqualsConditionOperator : ConditionOperator { diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NumberNotEqualConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/NumberNotEqualConditionOperator.cs index 6f5208cea..c0c29bb7a 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/NumberNotEqualConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/NumberNotEqualConditionOperator.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class NumberNotEqualConditionOperator : ConditionOperator { diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringContainsConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringContainsConditionOperator.cs index c108e7b5b..7df8eb9dc 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringContainsConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringContainsConditionOperator.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class StringContainsConditionOperator : ConditionOperator { diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringEndsWithConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringEndsWithConditionOperator.cs index 603660672..b8f5f91d0 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringEndsWithConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringEndsWithConditionOperator.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class StringEndsWithConditionOperator : ConditionOperator { diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringEqualsConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringEqualsConditionOperator.cs index 09bdbb502..970f96806 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringEqualsConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringEqualsConditionOperator.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class StringEqualsConditionOperator : ConditionOperator { diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringMatchesRegexConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringMatchesRegexConditionOperator.cs index 8b2c425c7..ada76f5f0 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringMatchesRegexConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringMatchesRegexConditionOperator.cs @@ -1,6 +1,6 @@ using System.Text.RegularExpressions; -namespace Artemis.Core.DefaultTypes { +namespace Artemis.Core { internal class StringMatchesRegexConditionOperator : ConditionOperator { public override string Description => "Matches Regex"; diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotContainsConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotContainsConditionOperator.cs index a15271341..f5b32c35c 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotContainsConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotContainsConditionOperator.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class StringNotContainsConditionOperator : ConditionOperator { diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotEqualConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotEqualConditionOperator.cs index 17b99227b..f51b63c4b 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotEqualConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotEqualConditionOperator.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class StringNotEqualConditionOperator : ConditionOperator { diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringStartsWithConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringStartsWithConditionOperator.cs index 538692baa..d43b6af20 100644 --- a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringStartsWithConditionOperator.cs +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringStartsWithConditionOperator.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class StringStartsWithConditionOperator : ConditionOperator { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs index 1a4c09f4e..9ffb4ec37 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { /// public class FloatDataBindingConverter : FloatDataBindingConverter diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/GeneralDataBindingConverter.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/GeneralDataBindingConverter.cs index fed1be236..b5672f3b1 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/GeneralDataBindingConverter.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/GeneralDataBindingConverter.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { /// /// Represents a generic data binding converter that acts as the bridge between a diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/IntDataBindingConverter.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/IntDataBindingConverter.cs index 879d2884f..795145630 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/IntDataBindingConverter.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/IntDataBindingConverter.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { /// public class IntDataBindingConverter : IntDataBindingConverter diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/SKColorDataBindingConverter.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/SKColorDataBindingConverter.cs index 917bc1ca8..003e06fa0 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/SKColorDataBindingConverter.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/SKColorDataBindingConverter.cs @@ -1,6 +1,6 @@ using SkiaSharp; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { /// public class SKColorDataBindingConverter : DataBindingConverter diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorBrightenModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorBrightenModifierType.cs index c99e407f3..32c4255ca 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorBrightenModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorBrightenModifierType.cs @@ -1,6 +1,6 @@ using SkiaSharp; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class SKColorBrightenModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorDarkenModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorDarkenModifierType.cs index a6ae0a1df..d26993e58 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorDarkenModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorDarkenModifierType.cs @@ -1,6 +1,6 @@ using SkiaSharp; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class SKColorDarkenModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorDesaturateModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorDesaturateModifierType.cs index 14c9d8790..e3d370ebd 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorDesaturateModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorDesaturateModifierType.cs @@ -1,7 +1,7 @@ using System; using SkiaSharp; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class SKColorDesaturateModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorInvertModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorInvertModifierType.cs index 804c551fc..bc46a053d 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorInvertModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorInvertModifierType.cs @@ -1,6 +1,6 @@ using SkiaSharp; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class SKColorInvertModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorRotateHueModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorRotateHueModifierType.cs index 24b787f16..45781ce48 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorRotateHueModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorRotateHueModifierType.cs @@ -1,6 +1,6 @@ using SkiaSharp; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class SKColorRotateHueModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorSaturateModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorSaturateModifierType.cs index 750915da6..751f96c24 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorSaturateModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorSaturateModifierType.cs @@ -1,7 +1,7 @@ using System; using SkiaSharp; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class SKColorSaturateModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorSumModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorSumModifierType.cs index 294c368d6..9c45436df 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorSumModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Colors/SKColorSumModifierType.cs @@ -1,6 +1,6 @@ using SkiaSharp; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class SKColorSumModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/AbsoluteModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/AbsoluteModifierType.cs index cf4aba7bc..c58bfd3b8 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/AbsoluteModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/AbsoluteModifierType.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class AbsoluteModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/DivideModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/DivideModifierType.cs index ce87e4158..215952095 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/DivideModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/DivideModifierType.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class DivideModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MaxModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MaxModifierType.cs index edd978108..f8da7936e 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MaxModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MaxModifierType.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class MaxModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MinModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MinModifierType.cs index 2b2913d9f..5ab519e01 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MinModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MinModifierType.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class MinModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/ModuloModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/ModuloModifierType.cs index b378f3174..8b710d3bf 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/ModuloModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/ModuloModifierType.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class ModuloModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MultiplicationModifier.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MultiplicationModifier.cs index 9992868e6..3198777cc 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MultiplicationModifier.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/MultiplicationModifier.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class MultiplicationModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/PercentageOfModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/PercentageOfModifierType.cs index bf435ca11..298d26e30 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/PercentageOfModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/PercentageOfModifierType.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class PercentageOfModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/PowerModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/PowerModifierType.cs index c5338510d..2ea1a0709 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/PowerModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/PowerModifierType.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class PowerModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/CeilingModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/CeilingModifierType.cs index a3f71d8e3..c4ca8f021 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/CeilingModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/CeilingModifierType.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class CeilingModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/FloorModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/FloorModifierType.cs index 77882edfe..e26173049 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/FloorModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/FloorModifierType.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class FloorModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/RoundModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/RoundModifierType.cs index b45f8fbd5..3b3283396 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/RoundModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Rounding/RoundModifierType.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class RoundModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SquareRootModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SquareRootModifierType.cs index fa8daed13..72dfa3f55 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SquareRootModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SquareRootModifierType.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class SquareRootModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SubtractModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SubtractModifierType.cs index 79cee880b..0dcda285a 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SubtractModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SubtractModifierType.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class SubtractModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SumModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SumModifierType.cs index eac8f9a15..3c2a816a6 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SumModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/SumModifierType.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class SumModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CosecantModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CosecantModifierType.cs index af974d203..201bb6f8b 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CosecantModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CosecantModifierType.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class CosecantModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CosineModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CosineModifierType.cs index b0fbf2932..3b4090654 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CosineModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CosineModifierType.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class CosineModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CotangentModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CotangentModifierType.cs index 76bd0adc6..dc95afffe 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CotangentModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/CotangentModifierType.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class CotangentModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/SecantModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/SecantModifierType.cs index f55f2982b..f597ad762 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/SecantModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/SecantModifierType.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class SecantModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/SineModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/SineModifierType.cs index ba502c6d0..5a315b316 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/SineModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/SineModifierType.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class SineModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/TangentModifierType.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/TangentModifierType.cs index 14c23d7ce..7a2b7c4ea 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/TangentModifierType.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Modifiers/Numbers/Trigonometry/TangentModifierType.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { internal class TangentModifierType : DataBindingModifierType { diff --git a/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs index c88ff368f..0390977f1 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { /// public class BoolLayerProperty : LayerProperty diff --git a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs index 92e9a22f8..e76348410 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { /// public class ColorGradientLayerProperty : LayerProperty diff --git a/src/Artemis.Core/DefaultTypes/Properties/EnumLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/EnumLayerProperty.cs index 9068441f3..eb99a3b3e 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/EnumLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/EnumLayerProperty.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { /// public class EnumLayerProperty : LayerProperty where T : Enum diff --git a/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs index 4ac6959c0..b9fc3cfd7 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs @@ -1,6 +1,4 @@ -using System; - -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { /// public class FloatLayerProperty : LayerProperty diff --git a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs index 64ff24c0a..1cb7c321a 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { /// public class FloatRangeLayerProperty : LayerProperty diff --git a/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs index 454f8328c..3ab64c02c 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { /// public class IntLayerProperty : LayerProperty diff --git a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs index 4aa74cbd0..dc9503520 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs @@ -1,6 +1,4 @@ -using System; - -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { /// public class IntRangeLayerProperty : LayerProperty diff --git a/src/Artemis.Core/DefaultTypes/Properties/LayerBrushReferenceLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/LayerBrushReferenceLayerProperty.cs index 2403b2f35..04eb19730 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/LayerBrushReferenceLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/LayerBrushReferenceLayerProperty.cs @@ -1,4 +1,4 @@ -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { /// /// A special layer property used to configure the selected layer brush diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs index 201086356..a7663a789 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs @@ -1,6 +1,6 @@ using SkiaSharp; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { /// public class SKColorLayerProperty : LayerProperty diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs index 312e20749..03c35baf2 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs @@ -1,6 +1,6 @@ using SkiaSharp; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { /// public class SKPointLayerProperty : LayerProperty diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs index ef2097c01..9a3707f3a 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs @@ -1,6 +1,6 @@ using SkiaSharp; -namespace Artemis.Core.DefaultTypes +namespace Artemis.Core { /// public class SKSizeLayerProperty : LayerProperty diff --git a/src/Artemis.Core/Events/Plugins/PluginEventArgs.cs b/src/Artemis.Core/Events/Plugins/PluginEventArgs.cs index 9f69f4974..48e4ec7db 100644 --- a/src/Artemis.Core/Events/Plugins/PluginEventArgs.cs +++ b/src/Artemis.Core/Events/Plugins/PluginEventArgs.cs @@ -8,11 +8,11 @@ namespace Artemis.Core { } - public PluginEventArgs(PluginInfo pluginInfo) + public PluginEventArgs(Plugin plugin) { - PluginInfo = pluginInfo; + Plugin = plugin; } - public PluginInfo PluginInfo { get; } + public Plugin Plugin { get; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Events/Plugins/PluginFeatureEventArgs.cs b/src/Artemis.Core/Events/Plugins/PluginFeatureEventArgs.cs new file mode 100644 index 000000000..288e46088 --- /dev/null +++ b/src/Artemis.Core/Events/Plugins/PluginFeatureEventArgs.cs @@ -0,0 +1,18 @@ +using System; + +namespace Artemis.Core +{ + public class PluginFeatureEventArgs : EventArgs + { + public PluginFeatureEventArgs() + { + } + + public PluginFeatureEventArgs(PluginFeature pluginFeature) + { + PluginFeature = pluginFeature; + } + + public PluginFeature PluginFeature { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Exceptions/ArtemisPluginException.cs b/src/Artemis.Core/Exceptions/ArtemisPluginException.cs index c0a889ce1..84c33d8ad 100644 --- a/src/Artemis.Core/Exceptions/ArtemisPluginException.cs +++ b/src/Artemis.Core/Exceptions/ArtemisPluginException.cs @@ -4,19 +4,19 @@ namespace Artemis.Core { public class ArtemisPluginException : Exception { - public ArtemisPluginException(PluginInfo pluginInfo) + public ArtemisPluginException(Plugin plugin) { - PluginInfo = pluginInfo; + Plugin = plugin; } - public ArtemisPluginException(PluginInfo pluginInfo, string message) : base(message) + public ArtemisPluginException(Plugin plugin, string message) : base(message) { - PluginInfo = pluginInfo; + Plugin = plugin; } - public ArtemisPluginException(PluginInfo pluginInfo, string message, Exception inner) : base(message, inner) + public ArtemisPluginException(Plugin plugin, string message, Exception inner) : base(message, inner) { - PluginInfo = pluginInfo; + Plugin = plugin; } public ArtemisPluginException(string message) : base(message) @@ -27,6 +27,6 @@ namespace Artemis.Core { } - public PluginInfo PluginInfo { get; } + public Plugin Plugin { get; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Exceptions/ArtemisPluginFeatureException.cs b/src/Artemis.Core/Exceptions/ArtemisPluginFeatureException.cs new file mode 100644 index 000000000..e15e7e313 --- /dev/null +++ b/src/Artemis.Core/Exceptions/ArtemisPluginFeatureException.cs @@ -0,0 +1,24 @@ +using System; + +namespace Artemis.Core +{ + public class ArtemisPluginFeatureException : Exception + { + public PluginFeature PluginFeature { get; } + + public ArtemisPluginFeatureException(PluginFeature pluginFeature) + { + PluginFeature = pluginFeature; + } + + public ArtemisPluginFeatureException(PluginFeature pluginFeature, string message) : base(message) + { + PluginFeature = pluginFeature; + } + + public ArtemisPluginFeatureException(PluginFeature pluginFeature, string message, Exception inner) : base(message, inner) + { + PluginFeature = pluginFeature; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/MVVM/CorePropertyChanged.cs b/src/Artemis.Core/MVVM/CorePropertyChanged.cs new file mode 100644 index 000000000..d4ff0419f --- /dev/null +++ b/src/Artemis.Core/MVVM/CorePropertyChanged.cs @@ -0,0 +1,72 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Artemis.Core +{ + /// + /// Represents a basic bindable class which notifies when a property value changes. + /// + public abstract class CorePropertyChanged : INotifyPropertyChanged + { + #region Events + + /// + /// Occurs when a property value changes. + /// + public event PropertyChangedEventHandler PropertyChanged; + + #endregion + + #region Methods + + /// + /// Checks if the property already matches the desirec value or needs to be updated. + /// + /// Type of the property. + /// Reference to the backing-filed. + /// Value to apply. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected virtual bool RequiresUpdate(ref T storage, T value) + { + return !Equals(storage, value); + } + + /// + /// Checks if the property already matches the desired value and updates it if not. + /// + /// Type of the property. + /// Reference to the backing-filed. + /// Value to apply. + /// + /// Name of the property used to notify listeners. This value is optional + /// and can be provided automatically when invoked from compilers that support + /// . + /// + /// true if the value was changed, false if the existing value matched the desired value. + protected virtual bool SetAndNotify(ref T storage, T value, [CallerMemberName] string propertyName = null) + { + if (!RequiresUpdate(ref storage, value)) return false; + + storage = value; + // ReSharper disable once ExplicitCallerInfoArgument + OnPropertyChanged(propertyName); + return true; + } + + /// + /// Triggers the -event when a a property value has changed. + /// + /// + /// Name of the property used to notify listeners. This value is optional + /// and can be provided automatically when invoked from compilers that support + /// . + /// + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs index ae94b9a55..da7a0e1d7 100644 --- a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs +++ b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Runtime.CompilerServices; using Artemis.Core.Properties; using SkiaSharp; -using Stylet; namespace Artemis.Core { @@ -19,13 +18,13 @@ namespace Artemis.Core /// public ColorGradient() { - Stops = new BindableCollection(); + Stops = new List(); } /// /// Gets a list of all the s in the gradient /// - public BindableCollection Stops { get; } + public List Stops { get; } /// /// Gets all the colors in the color gradient diff --git a/src/Artemis.Core/Models/Profile/Conditions/Abstract/BaseConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/BaseConditionOperator.cs index e67e73d78..f99c881a7 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Abstract/BaseConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Abstract/BaseConditionOperator.cs @@ -22,10 +22,10 @@ namespace Artemis.Core public abstract string Icon { get; } /// - /// Gets the plugin info this condition operator belongs to + /// Gets the plugin this condition operator belongs to /// Note: Not set until after registering /// - public PluginInfo PluginInfo { get; internal set; } + public Plugin Plugin { get; internal set; } /// /// Gets the left side type of this condition operator diff --git a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPredicate.cs index f99364cd2..32bb54423 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPredicate.cs @@ -346,7 +346,7 @@ namespace Artemis.Core if (Operator != null) { - Entity.OperatorPluginGuid = Operator.PluginInfo.Guid; + Entity.OperatorPluginGuid = Operator.Plugin.Guid; Entity.OperatorType = Operator.GetType().Name; } } @@ -358,7 +358,7 @@ namespace Artemis.Core private void ConditionOperatorStoreOnConditionOperatorAdded(object? sender, ConditionOperatorStoreEvent e) { BaseConditionOperator conditionOperator = e.Registration.ConditionOperator; - if (Entity.OperatorPluginGuid == conditionOperator.PluginInfo.Guid && Entity.OperatorType == conditionOperator.GetType().Name) + if (Entity.OperatorPluginGuid == conditionOperator.Plugin.Guid && Entity.OperatorType == conditionOperator.GetType().Name) UpdateOperator(conditionOperator); } diff --git a/src/Artemis.Core/Models/Profile/Conditions/Wrappers/EventPredicateWrapperDataModel.cs b/src/Artemis.Core/Models/Profile/Conditions/Wrappers/EventPredicateWrapperDataModel.cs index aee8d2adc..26768b06c 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Wrappers/EventPredicateWrapperDataModel.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Wrappers/EventPredicateWrapperDataModel.cs @@ -16,7 +16,7 @@ namespace Artemis.Core { internal EventPredicateWrapperDataModel() { - PluginInfo = Constants.CorePluginInfo; + Feature = Constants.CorePluginFeature; } [DataModelIgnore] diff --git a/src/Artemis.Core/Models/Profile/Conditions/Wrappers/ListPredicateWrapperDataModel.cs b/src/Artemis.Core/Models/Profile/Conditions/Wrappers/ListPredicateWrapperDataModel.cs index 1c8a3d458..bfa4396a5 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Wrappers/ListPredicateWrapperDataModel.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Wrappers/ListPredicateWrapperDataModel.cs @@ -16,7 +16,7 @@ namespace Artemis.Core { internal ListPredicateWrapperDataModel() { - PluginInfo = Constants.CorePluginInfo; + Feature = Constants.CorePluginFeature; } [DataModelIgnore] diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/BaseDataBindingModifierType.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/BaseDataBindingModifierType.cs index d4658d212..b3f2ec115 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/BaseDataBindingModifierType.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/BaseDataBindingModifierType.cs @@ -12,10 +12,10 @@ namespace Artemis.Core public abstract class BaseDataBindingModifierType { /// - /// Gets the plugin info this data binding modifier belongs to + /// Gets the plugin this data binding modifier belongs to /// Note: Not set until after registering /// - public PluginInfo PluginInfo { get; internal set; } + public Plugin Plugin { get; internal set; } /// /// Gets the value type of this modifier type diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/DataBindingModifier.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/DataBindingModifier.cs index 10732dfd3..02235faa2 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/DataBindingModifier.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/Modes/Direct/DataBindingModifier.cs @@ -238,7 +238,7 @@ namespace Artemis.Core if (ModifierType != null) { Entity.ModifierType = ModifierType.GetType().Name; - Entity.ModifierTypePluginGuid = ModifierType.PluginInfo.Guid; + Entity.ModifierTypePluginGuid = ModifierType.Plugin.Guid; } // General @@ -286,7 +286,7 @@ namespace Artemis.Core return; BaseDataBindingModifierType modifierType = e.TypeRegistration.DataBindingModifierType; - if (modifierType.PluginInfo.Guid == Entity.ModifierTypePluginGuid && modifierType.GetType().Name == Entity.ModifierType) + if (modifierType.Plugin.Guid == Entity.ModifierTypePluginGuid && modifierType.GetType().Name == Entity.ModifierType) UpdateModifierType(modifierType); } diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs index 054257402..f7ce3f942 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs @@ -91,9 +91,9 @@ namespace Artemis.Core public DataModel? Target { get; private set; } /// - /// Gets the data model GUID of the if it is a + /// Gets the data model ID of the if it is a /// - public Guid? DataModelGuid => Target?.PluginInfo.Guid; + public string? DataModelId => Target?.Feature.Id; /// /// Gets the point-separated path associated with this @@ -267,8 +267,8 @@ namespace Artemis.Core { Path = Entity.Path; - if (Target == null && Entity.DataModelGuid != null) - Target = DataModelStore.Get(Entity.DataModelGuid.Value)?.DataModel; + if (Target == null && Entity.DataModelId != null) + Target = DataModelStore.Get(Entity.DataModelId)?.DataModel; } /// @@ -279,7 +279,7 @@ namespace Artemis.Core return; Entity.Path = Path; - Entity.DataModelGuid = DataModelGuid; + Entity.DataModelId = DataModelId; Entity.WrapperType = Target switch { @@ -295,7 +295,7 @@ namespace Artemis.Core private void DataModelStoreOnDataModelAdded(object? sender, DataModelStoreEvent e) { - if (e.Registration.DataModel.PluginInfo.Guid != Entity.DataModelGuid) + if (e.Registration.DataModel.Feature.Id != Entity.DataModelId) return; Target = e.Registration.DataModel; @@ -304,7 +304,7 @@ namespace Artemis.Core private void DataModelStoreOnDataModelRemoved(object? sender, DataModelStoreEvent e) { - if (e.Registration.DataModel.PluginInfo.Guid != Entity.DataModelGuid) + if (e.Registration.DataModel.Feature.Id != Entity.DataModelId) return; Target = null; diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 9817e320f..b2de34b23 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -190,9 +190,9 @@ namespace Artemis.Core typeof(PropertyGroupDescriptionAttribute) ); General.GroupDescription = (PropertyGroupDescriptionAttribute) generalAttribute; - General.Initialize(this, "General.", Constants.CorePluginInfo); + General.Initialize(this, "General.", Constants.CorePluginFeature); Transform.GroupDescription = (PropertyGroupDescriptionAttribute) transformAttribute; - Transform.Initialize(this, "Transform.", Constants.CorePluginInfo); + Transform.Initialize(this, "Transform.", Constants.CorePluginFeature); General.ShapeType.CurrentValueSet += ShapeTypeOnCurrentValueSet; ApplyShapeType(); @@ -619,7 +619,7 @@ namespace Artemis.Core BaseLayerBrush brush = LayerBrush; DeactivateLayerBrush(); - LayerEntity.PropertyEntities.RemoveAll(p => p.PluginGuid == brush.PluginInfo.Guid && p.Path.StartsWith("LayerBrush.")); + LayerEntity.PropertyEntities.RemoveAll(p => p.FeatureId == brush.ProviderId && p.Path.StartsWith("LayerBrush.")); } internal void ActivateLayerBrush() @@ -628,7 +628,7 @@ namespace Artemis.Core if (current == null) return; - LayerBrushDescriptor descriptor = LayerBrushStore.Get(current.BrushPluginGuid, current.BrushType)?.LayerBrushDescriptor; + LayerBrushDescriptor? descriptor = LayerBrushStore.Get(current.LayerBrushProviderId, current.BrushType)?.LayerBrushDescriptor; descriptor?.CreateInstance(this); OnLayerBrushUpdated(); @@ -662,7 +662,7 @@ namespace Artemis.Core return; LayerBrushReference current = General.BrushReference.CurrentValue; - if (e.Registration.Plugin.PluginInfo.Guid == current.BrushPluginGuid && + if (e.Registration.PluginFeature.Id == current.LayerBrushProviderId && e.Registration.LayerBrushDescriptor.LayerBrushType.Name == current.BrushType) ActivateLayerBrush(); } diff --git a/src/Artemis.Core/Models/Profile/LayerBrushReference.cs b/src/Artemis.Core/Models/Profile/LayerBrushReference.cs index bcad54663..e9cf6d946 100644 --- a/src/Artemis.Core/Models/Profile/LayerBrushReference.cs +++ b/src/Artemis.Core/Models/Profile/LayerBrushReference.cs @@ -1,5 +1,4 @@ -using System; -using Artemis.Core.LayerBrushes; +using Artemis.Core.LayerBrushes; namespace Artemis.Core { @@ -8,24 +7,24 @@ namespace Artemis.Core /// public class LayerBrushReference { - /// - /// The GUID of the plugin the brush descriptor resides in - /// - public Guid BrushPluginGuid { get; set; } - - /// - /// The full type name of the brush descriptor - /// - public string BrushType { get; set; } - public LayerBrushReference() { } public LayerBrushReference(LayerBrushDescriptor descriptor) { - BrushPluginGuid = descriptor.LayerBrushProvider.PluginInfo.Guid; + LayerBrushProviderId = descriptor.Provider.Id; BrushType = descriptor.LayerBrushType.Name; } + + /// + /// The ID of the layer brush provided the brush was provided by + /// + public string LayerBrushProviderId { get; set; } + + /// + /// The full type name of the brush descriptor + /// + public string BrushType { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs b/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs index 4345c4be7..6f55911d7 100644 --- a/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs +++ b/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs @@ -1,5 +1,4 @@ -using Artemis.Core.DefaultTypes; -using SkiaSharp; +using SkiaSharp; namespace Artemis.Core { diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs index cdade5471..3afe363bd 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs @@ -1,12 +1,11 @@ using System; -using Stylet; namespace Artemis.Core { /// /// Represents a keyframe on a containing a value and a timestamp /// - public class LayerPropertyKeyframe : PropertyChangedBase + public class LayerPropertyKeyframe : CorePropertyChanged { private LayerProperty _layerProperty; private TimeSpan _position; diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index f27827c74..b3fbfe5ff 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -28,11 +28,11 @@ namespace Artemis.Core /// Gets the description of this group /// public PropertyGroupDescriptionAttribute GroupDescription { get; internal set; } - + /// - /// Gets the info of the plugin this group is associated with + /// Gets the plugin feature this group is associated with /// - public PluginInfo PluginInfo { get; internal set; } + public PluginFeature Feature { get; set; } /// /// Gets the profile element (such as layer or folder) this group is associated with @@ -145,18 +145,18 @@ namespace Artemis.Core PropertyGroupInitialized?.Invoke(this, EventArgs.Empty); } - internal void Initialize(RenderProfileElement profileElement, [NotNull] string path, PluginInfo pluginInfo) + internal void Initialize(RenderProfileElement profileElement, [NotNull] string path, PluginFeature feature) { if (path == null) throw new ArgumentNullException(nameof(path)); - if (pluginInfo == null) - throw new ArgumentNullException(nameof(pluginInfo)); + if (feature == null) + throw new ArgumentNullException(nameof(feature)); // Doubt this will happen but let's make sure if (PropertiesInitialized) throw new ArtemisCoreException("Layer property group already initialized, wut"); - PluginInfo = pluginInfo; + Feature = feature; ProfileElement = profileElement; Path = path.TrimEnd('.'); @@ -246,7 +246,7 @@ namespace Artemis.Core instance.GroupDescription = propertyGroupDescription; instance.LayerBrush = LayerBrush; instance.LayerEffect = LayerEffect; - instance.Initialize(ProfileElement, $"{path}{propertyInfo.Name}.", PluginInfo); + instance.Initialize(ProfileElement, $"{path}{propertyInfo.Name}.", Feature); propertyInfo.SetValue(this, instance); _layerPropertyGroups.Add(instance); @@ -254,11 +254,11 @@ namespace Artemis.Core private PropertyEntity GetPropertyEntity(RenderProfileElement profileElement, string path, out bool fromStorage) { - PropertyEntity entity = profileElement.RenderElementEntity.PropertyEntities.FirstOrDefault(p => p.PluginGuid == PluginInfo.Guid && p.Path == path); + PropertyEntity entity = profileElement.RenderElementEntity.PropertyEntities.FirstOrDefault(p => p.FeatureId == Feature.Id && p.Path == path); fromStorage = entity != null; if (entity == null) { - entity = new PropertyEntity {PluginGuid = PluginInfo.Guid, Path = path}; + entity = new PropertyEntity {FeatureId = Feature.Id, Path = path}; profileElement.RenderElementEntity.PropertyEntities.Add(entity); } diff --git a/src/Artemis.Core/Models/Profile/LayerTransformProperties.cs b/src/Artemis.Core/Models/Profile/LayerTransformProperties.cs index a0d858156..7c9d8c2ff 100644 --- a/src/Artemis.Core/Models/Profile/LayerTransformProperties.cs +++ b/src/Artemis.Core/Models/Profile/LayerTransformProperties.cs @@ -1,5 +1,4 @@ -using Artemis.Core.DefaultTypes; -using SkiaSharp; +using SkiaSharp; namespace Artemis.Core { diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index e92a80027..40d61fd06 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using Artemis.Core.Modules; using Artemis.Storage.Entities.Profile; -using Newtonsoft.Json; using SkiaSharp; namespace Artemis.Core @@ -147,7 +146,7 @@ namespace Artemis.Core throw new ObjectDisposedException("Profile"); ProfileEntity.Id = EntityId; - ProfileEntity.PluginGuid = Module.PluginInfo.Guid; + ProfileEntity.ModuleId = Module.Id; ProfileEntity.Name = Name; ProfileEntity.IsActive = IsActivated; diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs index 85cd09919..e55851f38 100644 --- a/src/Artemis.Core/Models/Profile/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs @@ -3,11 +3,10 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using SkiaSharp; -using Stylet; namespace Artemis.Core { - public abstract class ProfileElement : PropertyChangedBase, IDisposable + public abstract class ProfileElement : CorePropertyChanged, IDisposable { private bool _enabled; private Guid _entityId; diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index 27fe3246d..de9a7726a 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -44,7 +44,7 @@ namespace Artemis.Core LayerEffectEntity layerEffectEntity = new LayerEffectEntity { Id = layerEffect.EntityId, - PluginGuid = layerEffect.Descriptor.PlaceholderFor ?? layerEffect.PluginInfo.Guid, + ProviderId = layerEffect.Descriptor.PlaceholderFor ?? layerEffect.ProviderId, EffectType = layerEffect.GetEffectTypeName(), Name = layerEffect.Name, Enabled = layerEffect.Enabled, @@ -215,11 +215,11 @@ namespace Artemis.Core foreach (LayerEffectEntity layerEffectEntity in RenderElementEntity.LayerEffects) { // If there is a non-placeholder existing effect, skip this entity - BaseLayerEffect existing = _layerEffects.FirstOrDefault(e => e.EntityId == layerEffectEntity.Id); + BaseLayerEffect? existing = _layerEffects.FirstOrDefault(e => e.EntityId == layerEffectEntity.Id); if (existing != null && existing.Descriptor.PlaceholderFor == null) continue; - LayerEffectDescriptor descriptor = LayerEffectStore.Get(layerEffectEntity.PluginGuid, layerEffectEntity.EffectType)?.LayerEffectDescriptor; + LayerEffectDescriptor? descriptor = LayerEffectStore.Get(layerEffectEntity.ProviderId, layerEffectEntity.EffectType)?.LayerEffectDescriptor; if (descriptor != null) { // If a descriptor is found but there is an existing placeholder, remove the placeholder @@ -235,7 +235,7 @@ namespace Artemis.Core else if (existing == null) { // If no descriptor was found and there was no existing placeholder, create a placeholder - descriptor = PlaceholderLayerEffectDescriptor.Create(layerEffectEntity.PluginGuid); + descriptor = PlaceholderLayerEffectDescriptor.Create(layerEffectEntity.ProviderId); descriptor.CreateInstance(this, layerEffectEntity); } } @@ -253,22 +253,22 @@ namespace Artemis.Core private void LayerEffectStoreOnLayerEffectRemoved(object sender, LayerEffectStoreEvent e) { // If effects provided by the plugin are on the element, replace them with placeholders - List pluginEffects = _layerEffects.Where(ef => ef.Descriptor.LayerEffectProvider != null && - ef.PluginInfo.Guid == e.Registration.Plugin.PluginInfo.Guid).ToList(); + List pluginEffects = _layerEffects.Where(ef => ef.Descriptor.Provider != null && + ef.ProviderId == e.Registration.PluginFeature.Id).ToList(); foreach (BaseLayerEffect pluginEffect in pluginEffects) { LayerEffectEntity entity = RenderElementEntity.LayerEffects.First(en => en.Id == pluginEffect.EntityId); _layerEffects.Remove(pluginEffect); pluginEffect.Dispose(); - LayerEffectDescriptor descriptor = PlaceholderLayerEffectDescriptor.Create(pluginEffect.PluginInfo.Guid); + LayerEffectDescriptor descriptor = PlaceholderLayerEffectDescriptor.Create(pluginEffect.ProviderId); descriptor.CreateInstance(this, entity); } } private void LayerEffectStoreOnLayerEffectAdded(object sender, LayerEffectStoreEvent e) { - if (RenderElementEntity.LayerEffects.Any(ef => ef.PluginGuid == e.Registration.Plugin.PluginInfo.Guid)) + if (RenderElementEntity.LayerEffects.Any(ef => ef.ProviderId == e.Registration.PluginFeature.Id)) ActivateEffects(); } diff --git a/src/Artemis.Core/Models/Profile/Timeline.cs b/src/Artemis.Core/Models/Profile/Timeline.cs index 1f94376d9..f50e7d922 100644 --- a/src/Artemis.Core/Models/Profile/Timeline.cs +++ b/src/Artemis.Core/Models/Profile/Timeline.cs @@ -3,14 +3,13 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Artemis.Storage.Entities.Profile; -using Stylet; namespace Artemis.Core { /// /// Represents a timeline used by profile elements /// - public class Timeline : PropertyChangedBase, IStorageModel + public class Timeline : CorePropertyChanged, IStorageModel { private const int MaxExtraTimelines = 15; @@ -275,20 +274,20 @@ namespace Artemis.Core if (segment <= TimelineSegment.End) { if (startUpdated || segment < TimelineSegment.End) - NotifyOfPropertyChange(nameof(EndSegmentStartPosition)); - NotifyOfPropertyChange(nameof(EndSegmentEndPosition)); + OnPropertyChanged(nameof(EndSegmentStartPosition)); + OnPropertyChanged(nameof(EndSegmentEndPosition)); } if (segment <= TimelineSegment.Main) { if (startUpdated || segment < TimelineSegment.Main) - NotifyOfPropertyChange(nameof(MainSegmentStartPosition)); - NotifyOfPropertyChange(nameof(MainSegmentEndPosition)); + OnPropertyChanged(nameof(MainSegmentStartPosition)); + OnPropertyChanged(nameof(MainSegmentEndPosition)); } - if (segment <= TimelineSegment.Start) NotifyOfPropertyChange(nameof(StartSegmentEndPosition)); + if (segment <= TimelineSegment.Start) OnPropertyChanged(nameof(StartSegmentEndPosition)); - NotifyOfPropertyChange(nameof(Length)); + OnPropertyChanged(nameof(Length)); } #endregion diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs index fdef3a868..adf4e6d20 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs @@ -4,20 +4,19 @@ using System.Linq; using Artemis.Storage.Entities.Surface; using RGB.NET.Core; using SkiaSharp; -using Stylet; namespace Artemis.Core { - public class ArtemisDevice : PropertyChangedBase + public class ArtemisDevice : CorePropertyChanged { private ReadOnlyCollection _leds; private SKPath _renderPath; private SKRect _renderRectangle; - internal ArtemisDevice(IRGBDevice rgbDevice, Plugin plugin, ArtemisSurface surface) + internal ArtemisDevice(IRGBDevice rgbDevice, PluginFeature pluginFeature, ArtemisSurface surface) { RgbDevice = rgbDevice; - Plugin = plugin; + PluginFeature = pluginFeature; Surface = surface; DeviceEntity = new DeviceEntity(); Leds = rgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly(); @@ -30,10 +29,10 @@ namespace Artemis.Core CalculateRenderProperties(); } - internal ArtemisDevice(IRGBDevice rgbDevice, Plugin plugin, ArtemisSurface surface, DeviceEntity deviceEntity) + internal ArtemisDevice(IRGBDevice rgbDevice, PluginFeature pluginFeature, ArtemisSurface surface, DeviceEntity deviceEntity) { RgbDevice = rgbDevice; - Plugin = plugin; + PluginFeature = pluginFeature; Surface = surface; DeviceEntity = deviceEntity; Leds = rgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly(); @@ -52,7 +51,7 @@ namespace Artemis.Core } public IRGBDevice RgbDevice { get; } - public Plugin Plugin { get; } + public PluginFeature PluginFeature { get; } public ArtemisSurface Surface { get; } public DeviceEntity DeviceEntity { get; } @@ -68,7 +67,7 @@ namespace Artemis.Core set { DeviceEntity.X = value; - NotifyOfPropertyChange(nameof(X)); + OnPropertyChanged(nameof(X)); } } @@ -78,7 +77,7 @@ namespace Artemis.Core set { DeviceEntity.Y = value; - NotifyOfPropertyChange(nameof(Y)); + OnPropertyChanged(nameof(Y)); } } @@ -88,7 +87,7 @@ namespace Artemis.Core set { DeviceEntity.Rotation = value; - NotifyOfPropertyChange(nameof(Rotation)); + OnPropertyChanged(nameof(Rotation)); } } @@ -98,7 +97,7 @@ namespace Artemis.Core set { DeviceEntity.Scale = value; - NotifyOfPropertyChange(nameof(Scale)); + OnPropertyChanged(nameof(Scale)); } } @@ -108,7 +107,7 @@ namespace Artemis.Core set { DeviceEntity.ZIndex = value; - NotifyOfPropertyChange(nameof(ZIndex)); + OnPropertyChanged(nameof(ZIndex)); } } diff --git a/src/Artemis.Core/Models/Surface/ArtemisLed.cs b/src/Artemis.Core/Models/Surface/ArtemisLed.cs index d4f8f0546..0c55c356f 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisLed.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisLed.cs @@ -1,10 +1,9 @@ using RGB.NET.Core; using SkiaSharp; -using Stylet; namespace Artemis.Core { - public class ArtemisLed : PropertyChangedBase + public class ArtemisLed : CorePropertyChanged { private SKRect _absoluteRenderRectangle; private SKRect _renderRectangle; diff --git a/src/Artemis.Core/Models/Surface/ArtemisSurface.cs b/src/Artemis.Core/Models/Surface/ArtemisSurface.cs index 7b0a34376..f79040289 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisSurface.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisSurface.cs @@ -3,11 +3,10 @@ using System.Collections.Generic; using System.Linq; using Artemis.Storage.Entities.Surface; using RGB.NET.Core; -using Stylet; namespace Artemis.Core { - public class ArtemisSurface : PropertyChangedBase + public class ArtemisSurface : CorePropertyChanged { private List _devices; private bool _isActive; diff --git a/src/Artemis.Core/Ninject/CoreModule.cs b/src/Artemis.Core/Ninject/CoreModule.cs index 28fe3bcf0..5408951de 100644 --- a/src/Artemis.Core/Ninject/CoreModule.cs +++ b/src/Artemis.Core/Ninject/CoreModule.cs @@ -7,6 +7,7 @@ using LiteDB; using Ninject.Activation; using Ninject.Extensions.Conventions; using Ninject.Modules; +using Ninject.Planning.Bindings.Resolvers; using Serilog; namespace Artemis.Core.Ninject @@ -22,6 +23,8 @@ namespace Artemis.Core.Ninject if (Kernel == null) throw new ArtemisCoreException("Failed to bind Ninject Core module, kernel is null."); + Kernel.Components.Remove(); + // Bind all services as singletons Kernel.Bind(x => { @@ -88,6 +91,7 @@ namespace Artemis.Core.Ninject Kernel.Bind().ToProvider(); Kernel.Bind().ToProvider(); + Kernel.Bind().ToSelf(); } private bool HasAccessToProtectedService(IRequest r) diff --git a/src/Artemis.Core/Ninject/PluginModule.cs b/src/Artemis.Core/Ninject/PluginModule.cs index bd1b81ddd..f6909993b 100644 --- a/src/Artemis.Core/Ninject/PluginModule.cs +++ b/src/Artemis.Core/Ninject/PluginModule.cs @@ -1,19 +1,52 @@ using System; +using Artemis.Core.Services; +using Ninject.Extensions.Conventions; using Ninject.Modules; +using Ninject.Planning.Bindings.Resolvers; namespace Artemis.Core.Ninject { internal class PluginModule : NinjectModule { - public PluginModule(PluginInfo pluginInfo) + public PluginModule(Plugin plugin) { - PluginInfo = pluginInfo ?? throw new ArgumentNullException(nameof(pluginInfo)); + Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin)); } - public PluginInfo PluginInfo { get; } + public Plugin Plugin { get; } public override void Load() { + if (Kernel == null) + throw new ArtemisCoreException("Failed to bind plugin child module, kernel is null."); + + Kernel.Components.Remove(); + + Kernel.Bind().ToConstant(Plugin); + + // Bind plugin service interfaces + Kernel.Bind(x => + { + x.From(Plugin.Assembly) + .IncludingNonPublicTypes() + .SelectAllClasses() + .InheritedFrom() + .BindAllInterfaces() + .Configure(c => c.InSingletonScope()); + }); + + // Plugin developers may not use an interface so bind the plugin services to themselves + // Sadly if they do both, the kernel will treat the interface and the base type as two different singletons + // perhaps we can avoid that, but I'm not sure how + Kernel.Bind(x => + { + x.From(Plugin.Assembly) + .IncludingNonPublicTypes() + .SelectAllClasses() + .InheritedFrom() + .BindToSelf() + .Configure(c => c.InSingletonScope()); + }); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Ninject/PluginSettingsProvider.cs b/src/Artemis.Core/Ninject/PluginSettingsProvider.cs index d8ea16a06..66b707460 100644 --- a/src/Artemis.Core/Ninject/PluginSettingsProvider.cs +++ b/src/Artemis.Core/Ninject/PluginSettingsProvider.cs @@ -6,16 +6,17 @@ using Ninject.Activation; namespace Artemis.Core.Ninject { + // TODO: Investigate if this can't just be set as a constant on the plugin child kernel internal class PluginSettingsProvider : Provider { private static readonly List PluginSettings = new List(); private readonly IPluginRepository _pluginRepository; - private readonly IPluginService _pluginService; + private readonly IPluginManagementService _pluginManagementService; - internal PluginSettingsProvider(IPluginRepository pluginRepository, IPluginService pluginService) + public PluginSettingsProvider(IPluginRepository pluginRepository, IPluginManagementService pluginManagementService) { _pluginRepository = pluginRepository; - _pluginService = pluginService; + _pluginManagementService = pluginManagementService; } protected override PluginSettings CreateInstance(IContext context) @@ -25,21 +26,22 @@ namespace Artemis.Core.Ninject throw new ArtemisCoreException("PluginSettings couldn't be injected, failed to get the injection parent request"); // First try by PluginInfo parameter - PluginInfo pluginInfo = parentRequest.Parameters.FirstOrDefault(p => p.Name == "PluginInfo")?.GetValue(context, null) as PluginInfo; - if (pluginInfo == null) - pluginInfo = _pluginService.GetPluginByAssembly(parentRequest.Service.Assembly)?.PluginInfo; + Plugin? plugin = parentRequest.Parameters.FirstOrDefault(p => p.Name == "Plugin")?.GetValue(context, null) as Plugin; // Fall back to assembly based detection - if (pluginInfo == null) + if (plugin == null) + plugin = _pluginManagementService.GetPluginByAssembly(parentRequest.Service.Assembly); + + if (plugin == null) throw new ArtemisCoreException("PluginSettings can only be injected with the PluginInfo parameter provided " + "or into a class defined in a plugin assembly"); lock (PluginSettings) { - PluginSettings? existingSettings = PluginSettings.FirstOrDefault(p => p.PluginInfo == pluginInfo); + PluginSettings? existingSettings = PluginSettings.FirstOrDefault(p => p.Plugin == plugin); if (existingSettings != null) return existingSettings; - PluginSettings? settings = new PluginSettings(pluginInfo, _pluginRepository); + PluginSettings? settings = new PluginSettings(plugin, _pluginRepository); PluginSettings.Add(settings); return settings; } diff --git a/src/Artemis.Core/Ninject/SettingsServiceProvider.cs b/src/Artemis.Core/Ninject/SettingsServiceProvider.cs index 2188e4d4f..3a1f3efa0 100644 --- a/src/Artemis.Core/Ninject/SettingsServiceProvider.cs +++ b/src/Artemis.Core/Ninject/SettingsServiceProvider.cs @@ -17,7 +17,7 @@ namespace Artemis.Core.Ninject protected override ISettingsService CreateInstance(IContext context) { IRequest parentRequest = context.Request.ParentRequest; - if (parentRequest == null || typeof(Plugin).IsAssignableFrom(parentRequest.Service)) + if (parentRequest == null || typeof(PluginFeature).IsAssignableFrom(parentRequest.Service)) throw new ArtemisPluginException($"SettingsService can not be injected into a plugin. Inject {nameof(PluginSettings)} instead."); return _instance; diff --git a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs index b0ca3466c..dfbd52efd 100644 --- a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs +++ b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs @@ -14,10 +14,10 @@ namespace Artemis.Core.DataModelExpansions private readonly Dictionary _dynamicDataModels = new Dictionary(); /// - /// Gets the plugin info this data model belongs to + /// Gets the plugin feature this data model belongs to /// [DataModelIgnore] - public PluginInfo PluginInfo { get; internal set; } + public DataModelPluginFeature Feature { get; internal set; } /// /// Gets the describing this data model @@ -43,9 +43,9 @@ namespace Artemis.Core.DataModelExpansions /// public ReadOnlyCollection GetHiddenProperties() { - if (PluginInfo.Instance is ProfileModule profileModule) + if (Feature is ProfileModule profileModule) return profileModule.HiddenProperties; - if (PluginInfo.Instance is BaseDataModelExpansion dataModelExpansion) + if (Feature is BaseDataModelExpansion dataModelExpansion) return dataModelExpansion.HiddenProperties; return new List().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.PluginInfo = PluginInfo; + dynamicDataModel.Feature = Feature; dynamicDataModel.DataModelDescription = new DataModelPropertyAttribute { Name = string.IsNullOrWhiteSpace(name) ? key.Humanize() : name, diff --git a/src/Artemis.Core/Plugins/DataModelExpansions/DataModelExpansion.cs b/src/Artemis.Core/Plugins/DataModelExpansions/DataModelExpansion.cs index 03b38aa78..eca795b81 100644 --- a/src/Artemis.Core/Plugins/DataModelExpansions/DataModelExpansion.cs +++ b/src/Artemis.Core/Plugins/DataModelExpansions/DataModelExpansion.cs @@ -42,18 +42,18 @@ namespace Artemis.Core.DataModelExpansions HiddenPropertiesList.RemoveAll(p => p.Equals(propertyInfo)); } - internal override void InternalEnablePlugin() + internal override void InternalEnable() { DataModel = Activator.CreateInstance(); - DataModel.PluginInfo = PluginInfo; + DataModel.Feature = this; DataModel.DataModelDescription = GetDataModelDescription(); - base.InternalEnablePlugin(); + base.InternalEnable(); } - internal override void InternalDisablePlugin() + internal override void InternalDisable() { DataModel = null; - base.InternalDisablePlugin(); + base.InternalDisable(); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/DataModelExpansions/Internal/BaseDataModelExpansion.cs b/src/Artemis.Core/Plugins/DataModelExpansions/Internal/BaseDataModelExpansion.cs index 7dd9838fd..d11c81bd7 100644 --- a/src/Artemis.Core/Plugins/DataModelExpansions/Internal/BaseDataModelExpansion.cs +++ b/src/Artemis.Core/Plugins/DataModelExpansions/Internal/BaseDataModelExpansion.cs @@ -8,7 +8,7 @@ namespace Artemis.Core.DataModelExpansions /// For internal use only, to implement your own layer property type, extend /// instead. /// - public abstract class BaseDataModelExpansion : Plugin + public abstract class BaseDataModelExpansion : DataModelPluginFeature { /// /// Gets a list of all properties ignored at runtime using IgnoreProperty(x => x.y) @@ -20,7 +20,7 @@ namespace Artemis.Core.DataModelExpansions /// public ReadOnlyCollection HiddenProperties => HiddenPropertiesList.AsReadOnly(); - internal DataModel InternalDataModel { get; set; } + internal DataModel? InternalDataModel { get; set; } /// /// Called each frame when the data model should update @@ -28,6 +28,12 @@ namespace Artemis.Core.DataModelExpansions /// Time in seconds since the last update public abstract void Update(double deltaTime); + internal void InternalUpdate(double deltaTime) + { + if (InternalDataModel != null) + Update(deltaTime); + } + /// /// Override to provide your own data model description. By default this returns a description matching your plugin /// name and description @@ -35,7 +41,7 @@ namespace Artemis.Core.DataModelExpansions /// public virtual DataModelPropertyAttribute GetDataModelDescription() { - return new DataModelPropertyAttribute {Name = PluginInfo.Name, Description = PluginInfo.Description}; + return new DataModelPropertyAttribute {Name = Plugin.Info.Name, Description = Plugin.Info.Description}; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/DataModelPluginFeature.cs b/src/Artemis.Core/Plugins/DataModelPluginFeature.cs new file mode 100644 index 000000000..83649847a --- /dev/null +++ b/src/Artemis.Core/Plugins/DataModelPluginFeature.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading.Tasks; + +namespace Artemis.Core +{ + /// + /// Represents an feature of a certain type provided by a plugin with support for data models + /// + public abstract class DataModelPluginFeature : PluginFeature + { + /// + /// Registers a timed update that whenever the plugin is enabled calls the provided at the + /// provided + /// + /// + /// The interval at which the update should occur + /// + /// The action to call every time the interval has passed. The delta time parameter represents the + /// time passed since the last update in seconds + /// + /// The resulting plugin update registration which can be used to stop the update + public TimedUpdateRegistration AddTimedUpdate(TimeSpan interval, Action action) + { + if (action == null) + throw new ArgumentNullException(nameof(action)); + return new TimedUpdateRegistration(this, interval, action); + } + + /// + /// Registers a timed update that whenever the plugin is enabled calls the provided at the + /// provided + /// + /// + /// The interval at which the update should occur + /// + /// The async action to call every time the interval has passed. The delta time parameter + /// represents the time passed since the last update in seconds + /// + /// The resulting plugin update registration + public TimedUpdateRegistration AddTimedUpdate(TimeSpan interval, Func asyncAction) + { + if (asyncAction == null) + throw new ArgumentNullException(nameof(asyncAction)); + return new TimedUpdateRegistration(this, interval, asyncAction); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs b/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs index 9ddbbf919..c59446c14 100644 --- a/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs +++ b/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs @@ -10,7 +10,7 @@ namespace Artemis.Core.DeviceProviders /// /// Allows you to implement and register your own device provider /// - public abstract class DeviceProvider : Plugin + public abstract class DeviceProvider : PluginFeature { /// /// Creates a new instance of the class @@ -34,7 +34,7 @@ namespace Artemis.Core.DeviceProviders public ILogger Logger { get; set; } /// - public override void DisablePlugin() + public override void Disable() { // Does not happen with device providers, they require Artemis to restart } @@ -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)) diff --git a/src/Artemis.Core/Plugins/IPluginBootstrapper.cs b/src/Artemis.Core/Plugins/IPluginBootstrapper.cs new file mode 100644 index 000000000..af4b607d7 --- /dev/null +++ b/src/Artemis.Core/Plugins/IPluginBootstrapper.cs @@ -0,0 +1,20 @@ +namespace Artemis.Core +{ + /// + /// An optional entry point for your plugin + /// + public interface IPluginBootstrapper + { + /// + /// Called when the plugin is activated + /// + /// The plugin instance of your plugin + void Enable(Plugin plugin); + + /// + /// Called when the plugin is deactivated or when Artemis shuts down + /// + /// The plugin instance of your plugin + void Disable(Plugin plugin); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/IPluginConfigurationDialog.cs b/src/Artemis.Core/Plugins/IPluginConfigurationDialog.cs new file mode 100644 index 000000000..c76d777ca --- /dev/null +++ b/src/Artemis.Core/Plugins/IPluginConfigurationDialog.cs @@ -0,0 +1,7 @@ +namespace Artemis.Core +{ + public interface IPluginConfigurationDialog + { + + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/IPluginConfigurationViewModel.cs b/src/Artemis.Core/Plugins/IPluginConfigurationViewModel.cs new file mode 100644 index 000000000..f2e711397 --- /dev/null +++ b/src/Artemis.Core/Plugins/IPluginConfigurationViewModel.cs @@ -0,0 +1,10 @@ +namespace Artemis.Core +{ + /// + /// Represents a view model for a plugin configuration window + /// + public interface IPluginConfigurationViewModel + { + + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrushes/ILayerBrushConfigurationDialog.cs b/src/Artemis.Core/Plugins/LayerBrushes/ILayerBrushConfigurationDialog.cs new file mode 100644 index 000000000..c0ad0df9d --- /dev/null +++ b/src/Artemis.Core/Plugins/LayerBrushes/ILayerBrushConfigurationDialog.cs @@ -0,0 +1,9 @@ +namespace Artemis.Core.LayerBrushes +{ + /// + /// Represents the configuration dialog of a layer brush + /// + public interface ILayerBrushConfigurationDialog + { + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs index 847d5cd66..fd399e1ad 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs @@ -1,17 +1,15 @@ using System; -using Artemis.Core.Services; using SkiaSharp; -using Stylet; namespace Artemis.Core.LayerBrushes { /// /// For internal use only, please use or or instead /// - 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 /// /// Gets or sets a configuration dialog complementing the regular properties /// - public LayerBrushConfigurationDialog ConfigurationDialog + public ILayerBrushConfigurationDialog ConfigurationDialog { get => _configurationDialog; protected set => SetAndNotify(ref _configurationDialog, value); @@ -53,9 +51,9 @@ namespace Artemis.Core.LayerBrushes } /// - /// Gets the plugin info that defined this brush + /// Gets the ID of the that provided this effect /// - public PluginInfo PluginInfo => Descriptor.LayerBrushProvider.PluginInfo; + public string ProviderId => Descriptor?.Provider?.Id; /// /// 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; } } - /// - public void Dispose() - { - DisableLayerBrush(); - BaseProperties.Dispose(); - - Dispose(true); - GC.SuppressFinalize(this); - } - /// /// Called when the layer brush is activated /// @@ -112,6 +100,16 @@ namespace Artemis.Core.LayerBrushes internal virtual void Dispose(bool disposing) { } + + /// + public void Dispose() + { + DisableLayerBrush(); + BaseProperties.Dispose(); + + Dispose(true); + GC.SuppressFinalize(this); + } } /// diff --git a/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs index 88c2d3421..86cac67a7 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs @@ -37,7 +37,7 @@ namespace Artemis.Core.LayerBrushes Properties = Activator.CreateInstance(); 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(); diff --git a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs index b319ed448..4b1da7753 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs @@ -1,5 +1,4 @@ using System; -using Artemis.Core.Services; using Ninject; namespace Artemis.Core.LayerBrushes @@ -9,13 +8,13 @@ namespace Artemis.Core.LayerBrushes /// 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; } /// @@ -42,7 +41,7 @@ namespace Artemis.Core.LayerBrushes /// /// The plugin that provided this /// - public LayerBrushProvider LayerBrushProvider { get; } + public LayerBrushProvider Provider { get; } /// /// Determines whether the provided 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; } /// @@ -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(); diff --git a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushProvider.cs b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushProvider.cs index 6994f60d1..a828c8d25 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushProvider.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushProvider.cs @@ -7,7 +7,7 @@ namespace Artemis.Core.LayerBrushes /// /// Allows you to create one or more s usable by profile layers. /// - public abstract class LayerBrushProvider : Plugin + public abstract class LayerBrushProvider : PluginFeature { private readonly List _layerBrushDescriptors; @@ -17,7 +17,7 @@ namespace Artemis.Core.LayerBrushes protected LayerBrushProvider() { _layerBrushDescriptors = new List(); - PluginDisabled += OnPluginDisabled; + Disabled += OnDisabled; } /// @@ -38,15 +38,15 @@ namespace Artemis.Core.LayerBrushes /// protected void RegisterLayerBrushDescriptor(string displayName, string description, string icon) where T : BaseLayerBrush { - if (!Enabled) - throw new ArtemisPluginException(PluginInfo, "Can only add a layer brush descriptor when the plugin is enabled"); + if (!IsEnabled) + 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); LayerBrushStore.Add(descriptor); } - private void OnPluginDisabled(object sender, EventArgs e) + private void OnDisabled(object sender, EventArgs e) { // The store will clean up the registrations by itself, the plugin just needs to clear its own list _layerBrushDescriptors.Clear(); diff --git a/src/Artemis.Core/Plugins/LayerEffects/IEffectConfigurationViewModel.cs b/src/Artemis.Core/Plugins/LayerEffects/IEffectConfigurationViewModel.cs new file mode 100644 index 000000000..33aa630e9 --- /dev/null +++ b/src/Artemis.Core/Plugins/LayerEffects/IEffectConfigurationViewModel.cs @@ -0,0 +1,6 @@ +namespace Artemis.Core.LayerEffects +{ + public interface IEffectConfigurationViewModel + { + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerEffects/ILayerEffectConfigurationDialog.cs b/src/Artemis.Core/Plugins/LayerEffects/ILayerEffectConfigurationDialog.cs new file mode 100644 index 000000000..bafe13a1d --- /dev/null +++ b/src/Artemis.Core/Plugins/LayerEffects/ILayerEffectConfigurationDialog.cs @@ -0,0 +1,6 @@ +namespace Artemis.Core.LayerEffects +{ + public interface ILayerEffectConfigurationDialog + { + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs index 7e5778301..126a1e700 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs @@ -1,16 +1,15 @@ using System; using Artemis.Core.Services; using SkiaSharp; -using Stylet; namespace Artemis.Core.LayerEffects { /// /// For internal use only, please use instead /// - 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 /// /// Gets the that registered this effect /// - public LayerEffectDescriptor Descriptor + public LayerEffectDescriptor? Descriptor { get => _descriptor; internal set => SetAndNotify(ref _descriptor, value); @@ -86,16 +85,16 @@ namespace Artemis.Core.LayerEffects /// /// Gets or sets a configuration dialog complementing the regular properties /// - public LayerEffectConfigurationDialog ConfigurationDialog + public ILayerEffectConfigurationDialog ConfigurationDialog { get => _configurationDialog; protected set => SetAndNotify(ref _configurationDialog, value); } /// - /// Gets the plugin info that defined this effect + /// Gets the ID of the that provided this effect /// - public PluginInfo PluginInfo => Descriptor.LayerEffectProvider?.PluginInfo; + public string ProviderId => Descriptor?.Provider?.Id; /// /// Gets a reference to the layer property group without knowing it's type diff --git a/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs index 180e5b1a5..75bcce09e 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs @@ -37,7 +37,7 @@ namespace Artemis.Core.LayerEffects { Properties = Activator.CreateInstance(); Properties.LayerEffect = this; - Properties.Initialize(ProfileElement, PropertyRootPath, PluginInfo); + Properties.Initialize(ProfileElement, PropertyRootPath, Descriptor.Provider); PropertiesInitialized = true; EnableLayerEffect(); diff --git a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs index 97f27da49..33fafb542 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs @@ -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 /// 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; } /// @@ -45,12 +44,12 @@ namespace Artemis.Core.LayerEffects /// /// The plugin that provided this /// - public LayerEffectProvider LayerEffectProvider { get; } + public LayerEffectProvider? Provider { get; } /// /// Gets the GUID this descriptor is acting as a placeholder for. If null, this descriptor is not a placeholder /// - public Guid? PlaceholderFor { get; internal set; } + public string? PlaceholderFor { get; internal set; } /// /// 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); } diff --git a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectProvider.cs b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectProvider.cs index c578d9667..b6b68df7a 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectProvider.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectProvider.cs @@ -7,7 +7,7 @@ namespace Artemis.Core.LayerEffects /// /// Allows you to register one or more s usable by profile layers. /// - public abstract class LayerEffectProvider : Plugin + public abstract class LayerEffectProvider : PluginFeature { private readonly List _layerEffectDescriptors; @@ -17,7 +17,7 @@ namespace Artemis.Core.LayerEffects protected LayerEffectProvider() { _layerEffectDescriptors = new List(); - PluginDisabled += OnPluginDisabled; + Disabled += OnDisabled; } /// @@ -38,15 +38,15 @@ namespace Artemis.Core.LayerEffects /// protected void RegisterLayerEffectDescriptor(string displayName, string description, string icon) where T : BaseLayerEffect { - if (!Enabled) - throw new ArtemisPluginException(PluginInfo, "Can only add a layer effect descriptor when the plugin is enabled"); + if (!IsEnabled) + 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); LayerEffectStore.Add(descriptor); } - private void OnPluginDisabled(object sender, EventArgs e) + private void OnDisabled(object sender, EventArgs e) { // The store will clean up the registrations by itself, the plugin just needs to clear its own list _layerEffectDescriptors.Clear(); diff --git a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs index 6447679d0..620425a62 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs @@ -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 /// internal class PlaceholderLayerEffect : LayerEffect { - 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; } /// public override void EnableLayerEffect() @@ -56,7 +56,7 @@ namespace Artemis.Core.LayerEffects.Placeholder } /// - /// This is in place so that the UI has something to show + /// This is in place so that the UI has something to show /// internal class PlaceholderProperties : LayerPropertyGroup { diff --git a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs index eec2835fb..762c187d6 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs @@ -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; diff --git a/src/Artemis.Core/Plugins/Modules/IModuleViewModel.cs b/src/Artemis.Core/Plugins/Modules/IModuleViewModel.cs new file mode 100644 index 000000000..861336edc --- /dev/null +++ b/src/Artemis.Core/Plugins/Modules/IModuleViewModel.cs @@ -0,0 +1,10 @@ +namespace Artemis.Core.Modules +{ + /// + /// The base class for any view model that belongs to a module + /// + public interface IModuleViewModel + { + + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Modules/Module.cs b/src/Artemis.Core/Plugins/Modules/Module.cs index e6725a329..8c3c3ee45 100644 --- a/src/Artemis.Core/Plugins/Modules/Module.cs +++ b/src/Artemis.Core/Plugins/Modules/Module.cs @@ -42,21 +42,21 @@ namespace Artemis.Core.Modules /// 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 InternalEnablePlugin() + internal override void InternalEnable() { DataModel = Activator.CreateInstance(); - DataModel.PluginInfo = PluginInfo; + DataModel.Feature = this; DataModel.DataModelDescription = GetDataModelDescription(); - base.InternalEnablePlugin(); + base.InternalEnable(); } - internal override void InternalDisablePlugin() + internal override void InternalDisable() { DataModel = null; - base.InternalDisablePlugin(); + base.InternalDisable(); } } @@ -64,7 +64,7 @@ namespace Artemis.Core.Modules /// /// Allows you to add support for new games/applications /// - public abstract class Module : Plugin + public abstract class Module : DataModelPluginFeature { /// /// 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; } diff --git a/src/Artemis.Core/Plugins/Modules/ModuleTab.cs b/src/Artemis.Core/Plugins/Modules/ModuleTab.cs index 7c868b8bd..1c017964c 100644 --- a/src/Artemis.Core/Plugins/Modules/ModuleTab.cs +++ b/src/Artemis.Core/Plugins/Modules/ModuleTab.cs @@ -3,7 +3,7 @@ namespace Artemis.Core.Modules { /// - public class ModuleTab : ModuleTab where T : ModuleViewModel + public class ModuleTab : ModuleTab where T : IModuleViewModel { /// /// Initializes a new instance of the class diff --git a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs index 6b5b452e8..90e04a005 100644 --- a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs +++ b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs @@ -46,7 +46,7 @@ namespace Artemis.Core.Modules /// public virtual DataModelPropertyAttribute GetDataModelDescription() { - return new DataModelPropertyAttribute {Name = PluginInfo.Name, Description = PluginInfo.Description}; + return new DataModelPropertyAttribute {Name = Plugin.Info.Name, Description = Plugin.Info.Description}; } /// @@ -71,18 +71,18 @@ namespace Artemis.Core.Modules HiddenPropertiesList.RemoveAll(p => p.Equals(propertyInfo)); } - internal override void InternalEnablePlugin() + internal override void InternalEnable() { DataModel = Activator.CreateInstance(); - DataModel.PluginInfo = PluginInfo; + DataModel.Feature = this; DataModel.DataModelDescription = GetDataModelDescription(); - base.InternalEnablePlugin(); + base.InternalEnable(); } - internal override void InternalDisablePlugin() + internal override void InternalDisable() { Deactivate(true); - base.InternalDisablePlugin(); + base.InternalDisable(); DataModel = null; } } @@ -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"); diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs index 5abb34c91..1182e8920 100644 --- a/src/Artemis.Core/Plugins/Plugin.cs +++ b/src/Artemis.Core/Plugins/Plugin.cs @@ -1,148 +1,175 @@ using System; -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Reflection; +using Artemis.Storage.Entities.Plugins; +using McMaster.NETCore.Plugins; +using Ninject; namespace Artemis.Core { /// - /// This is the base plugin type, use the other interfaces such as Module to create plugins + /// Represents a plugin /// - public abstract class Plugin : IDisposable + public class Plugin : CorePropertyChanged, IDisposable { + private readonly List _features; + + private bool _isEnabled; + + internal Plugin(PluginInfo info, DirectoryInfo directory) + { + Info = info; + Directory = directory; + + _features = new List(); + } + + /// + /// Gets the plugin GUID + /// + public Guid Guid => Info.Guid; + /// /// Gets the plugin info related to this plugin /// - public PluginInfo PluginInfo { get; internal set; } + public PluginInfo Info { get; } /// - /// Gets whether the plugin is enabled + /// The plugins root directory /// - public bool Enabled { get; internal set; } + public DirectoryInfo Directory { get; } /// /// Gets or sets a configuration dialog for this plugin that is accessible in the UI under Settings > Plugins /// - public PluginConfigurationDialog ConfigurationDialog { get; protected set; } + public IPluginConfigurationDialog? ConfigurationDialog { get; set; } /// - /// Called when the plugin is activated + /// Indicates whether the user enabled the plugin or not /// - public abstract void EnablePlugin(); - - /// - /// Called when the plugin is deactivated or when Artemis shuts down - /// - public abstract void DisablePlugin(); - - /// - /// Registers a timed update that whenever the plugin is enabled calls the provided at the - /// provided - /// - /// - /// The interval at which the update should occur - /// - /// The action to call every time the interval has passed. The delta time parameter represents the - /// time passed since the last update in seconds - /// - /// The resulting plugin update registration which can be used to stop the update - public PluginUpdateRegistration AddTimedUpdate(TimeSpan interval, Action action) + public bool IsEnabled { - if (action == null) - throw new ArgumentNullException(nameof(action)); - return new PluginUpdateRegistration(PluginInfo, interval, action); + get => _isEnabled; + private set => SetAndNotify(ref _isEnabled, value); } /// - /// Registers a timed update that whenever the plugin is enabled calls the provided at the - /// provided - /// + /// Gets a read-only collection of all features this plugin provides /// - /// The interval at which the update should occur - /// - /// The async action to call every time the interval has passed. The delta time parameter - /// represents the time passed since the last update in seconds - /// - /// The resulting plugin update registration - public PluginUpdateRegistration AddTimedUpdate(TimeSpan interval, Func asyncAction) + public ReadOnlyCollection Features => _features.AsReadOnly(); + + /// + /// The assembly the plugin code lives in + /// + public Assembly? Assembly { get; internal set; } + + /// + /// Gets the plugin bootstrapper + /// + public IPluginBootstrapper? 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; } + + /// + /// 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 + public string? ResolveRelativePath(string path) { - if (asyncAction == null) - throw new ArgumentNullException(nameof(asyncAction)); - return new PluginUpdateRegistration(PluginInfo, interval, asyncAction); + return path == null ? null : Path.Combine(Directory.FullName, path); } - internal void SetEnabled(bool enable, bool isAutoEnable = false) + /// + /// 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 + public T? GetFeature() where T : PluginFeature { - if (enable && !Enabled) - { - try - { - if (isAutoEnable && PluginInfo.GetLockFileCreated()) - { - // Don't wrap existing lock exceptions, simply rethrow them - if (PluginInfo.LoadException is ArtemisPluginLockException) - throw PluginInfo.LoadException; - - throw new ArtemisPluginLockException(PluginInfo.LoadException); - } - - Enabled = true; - PluginInfo.Enabled = true; - PluginInfo.CreateLockFile(); - - // 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 - // Little meh: Running this from a different thread could cause deadlocks - Task enableTask = Task.Run(InternalEnablePlugin); - if (!enableTask.Wait(TimeSpan.FromSeconds(15))) - throw new ArtemisPluginException(PluginInfo, "Plugin load timeout"); - - PluginInfo.LoadException = null; - OnPluginEnabled(); - } - // If enable failed, put it back in a disabled state - catch (Exception e) - { - Enabled = false; - PluginInfo.Enabled = false; - PluginInfo.LoadException = e; - throw; - } - finally - { - if (!(PluginInfo.LoadException is ArtemisPluginLockException)) - PluginInfo.DeleteLockFile(); - } - } - else if (!enable && Enabled) - { - Enabled = false; - PluginInfo.Enabled = false; - - // Even if disable failed, still leave it in a disabled state to avoid more issues - InternalDisablePlugin(); - OnPluginDisabled(); - } - // A failed load is still enabled in plugin info (to avoid disabling it permanently after a fail) - // update even that when manually disabling - else if (!enable && !Enabled) - { - PluginInfo.Enabled = false; - } - } - - internal virtual void InternalEnablePlugin() - { - EnablePlugin(); - } - - internal virtual void InternalDisablePlugin() - { - DisablePlugin(); + return _features.FirstOrDefault(i => i is T) as T; } /// + public override string ToString() + { + 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) + { + if (feature.IsEnabled) + throw new ArtemisCoreException("Cannot remove an enabled feature from a plugin"); + + _features.Remove(feature); + 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() { - DisablePlugin(); + foreach (PluginFeature feature in Features) + feature.Dispose(); + + Kernel?.Dispose(); + PluginLoader?.Dispose(); + + _features.Clear(); + SetEnabled(false); } #region Events @@ -150,27 +177,53 @@ namespace Artemis.Core /// /// Occurs when the plugin is enabled /// - public event EventHandler PluginEnabled; + public event EventHandler? Enabled; /// /// Occurs when the plugin is disabled /// - public event EventHandler PluginDisabled; + public event EventHandler? Disabled; /// - /// Triggers the PluginEnabled event + /// Occurs when an feature is loaded and added to the plugin /// - protected virtual void OnPluginEnabled() + 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() { - PluginEnabled?.Invoke(this, EventArgs.Empty); + Enabled?.Invoke(this, EventArgs.Empty); } /// - /// Triggers the PluginDisabled event + /// Invokes the Disabled event /// - protected virtual void OnPluginDisabled() + protected virtual void OnDisabled() { - PluginDisabled?.Invoke(this, EventArgs.Empty); + Disabled?.Invoke(this, EventArgs.Empty); + } + + /// + /// Invokes the FeatureAdded event + /// + protected virtual void OnFeatureAdded(PluginFeatureEventArgs e) + { + FeatureAdded?.Invoke(this, e); + } + + /// + /// Invokes the FeatureRemoved event + /// + protected virtual void OnFeatureRemoved(PluginFeatureEventArgs e) + { + FeatureRemoved?.Invoke(this, e); } #endregion diff --git a/src/Artemis.Core/Plugins/PluginFeature.cs b/src/Artemis.Core/Plugins/PluginFeature.cs new file mode 100644 index 000000000..06f09b90b --- /dev/null +++ b/src/Artemis.Core/Plugins/PluginFeature.cs @@ -0,0 +1,195 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Artemis.Storage.Entities.Plugins; + +namespace Artemis.Core +{ + /// + /// Represents an feature of a certain type provided by a plugin + /// + public abstract class PluginFeature : CorePropertyChanged, IDisposable + { + private bool _isEnabled; + private Exception? _loadException; + + /// + /// Gets the plugin that provides this feature + /// + public Plugin Plugin { get; internal set; } + + /// + /// Gets whether the plugin is enabled + /// + public bool IsEnabled + { + get => _isEnabled; + internal set => SetAndNotify(ref _isEnabled, value); + } + + /// + /// Gets the exception thrown while loading + /// + public Exception? LoadException + { + get => _loadException; + internal set => SetAndNotify(ref _loadException, value); + } + + /// + /// Gets the identifier of this plugin feature + /// + 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; } + + /// + /// Called when the feature is activated + /// + public abstract void Enable(); + + /// + /// Called when the feature is deactivated or when Artemis shuts down + /// + public abstract void Disable(); + + internal void SetEnabled(bool enable, bool isAutoEnable = false) + { + if (enable == IsEnabled) + return; + + if (Plugin == null) + 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 feature of a disabled plugin"); + + if (!enable) + { + IsEnabled = false; + + // Even if disable failed, still leave it in a disabled state to avoid more issues + InternalDisable(); + OnDisabled(); + return; + } + + try + { + if (isAutoEnable && GetLockFileCreated()) + { + // Don't wrap existing lock exceptions, simply rethrow them + if (LoadException is ArtemisPluginLockException) + throw LoadException; + + throw new ArtemisPluginLockException(LoadException); + } + + CreateLockFile(); + IsEnabled = true; + + // 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 + // 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(); + } + // If enable failed, put it back in a disabled state + catch (Exception e) + { + IsEnabled = false; + LoadException = e; + throw; + } + finally + { + // Clean up the lock file unless the failure was due to the lock file + // After all, we failed but not miserably :) + if (!(LoadException is ArtemisPluginLockException)) + DeleteLockFile(); + } + } + } + + internal virtual void InternalEnable() + { + Enable(); + } + + internal virtual void InternalDisable() + { + Disable(); + } + + /// + public void Dispose() + { + Disable(); + } + + #region Loading + + internal void CreateLockFile() + { + if (Plugin == null) + throw new ArtemisCoreException("Cannot lock a plugin feature that is not associated with a plugin"); + + File.Create(Plugin.ResolveRelativePath($"{GetType().FullName}.lock")).Close(); + } + + internal void DeleteLockFile() + { + if (Plugin == null) + throw new ArtemisCoreException("Cannot lock a plugin feature that is not associated with a plugin"); + + if (GetLockFileCreated()) + File.Delete(Plugin.ResolveRelativePath($"{GetType().FullName}.lock")); + } + + internal bool GetLockFileCreated() + { + if (Plugin == null) + throw new ArtemisCoreException("Cannot lock a plugin feature that is not associated with a plugin"); + + return File.Exists(Plugin.ResolveRelativePath($"{GetType().FullName}.lock")); + } + + #endregion + + #region Events + + /// + /// Occurs when the feature is enabled + /// + public event EventHandler? Enabled; + + /// + /// Occurs when the feature is disabled + /// + public event EventHandler? Disabled; + + /// + /// Triggers the Enabled event + /// + protected virtual void OnEnabled() + { + Enabled?.Invoke(this, EventArgs.Empty); + } + + /// + /// Triggers the Disabled event + /// + protected virtual void OnDisabled() + { + Disabled?.Invoke(this, EventArgs.Empty); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/PluginInfo.cs b/src/Artemis.Core/Plugins/PluginInfo.cs index 1c071d7cd..26a3b7e1f 100644 --- a/src/Artemis.Core/Plugins/PluginInfo.cs +++ b/src/Artemis.Core/Plugins/PluginInfo.cs @@ -1,12 +1,5 @@ using System; -using System.IO; -using System.Reflection; -using Artemis.Storage.Entities.Plugins; -using McMaster.NETCore.Plugins; using Newtonsoft.Json; -using Ninject; -using Ninject.Extensions.ChildKernel; -using Stylet; namespace Artemis.Core { @@ -14,17 +7,14 @@ namespace Artemis.Core /// Represents basic info about a plugin and contains a reference to the instance of said plugin /// [JsonObject(MemberSerialization.OptIn)] - public class PluginInfo : PropertyChangedBase + public class PluginInfo : CorePropertyChanged { private string _description; - private DirectoryInfo _directory; - private bool _enabled; private Guid _guid; private string _icon; - private Plugin _instance; - private Exception _loadException; private string _main; private string _name; + private Plugin _plugin; private Version _version; internal PluginInfo() @@ -94,92 +84,18 @@ namespace Artemis.Core } /// - /// The plugins root directory + /// Gets the plugin this info is associated with /// - public DirectoryInfo Directory + public Plugin Plugin { - get => _directory; - internal set => SetAndNotify(ref _directory, value); + get => _plugin; + internal set => SetAndNotify(ref _plugin, value); } - /// - /// A reference to the type implementing Plugin, available after successful load - /// - public Plugin Instance - { - get => _instance; - internal set => SetAndNotify(ref _instance, value); - } - - /// - /// Indicates whether the user enabled the plugin or not - /// - public bool Enabled - { - get => _enabled; - internal set => SetAndNotify(ref _enabled, value); - } - - /// - /// Gets the exception thrown while loading - /// - public Exception LoadException - { - get => _loadException; - internal set => SetAndNotify(ref _loadException, value); - } - - /// - /// The PluginLoader backing this plugin - /// - internal PluginLoader PluginLoader { get; set; } - - /// - /// The assembly the plugin code lives in - /// - public Assembly Assembly { get; internal set; } - - /// - /// The Ninject kernel of the plugin - /// - public IKernel Kernel { get; internal set; } - - /// - /// The entity representing the plugin - /// - internal PluginEntity PluginEntity { get; set; } - /// public override string ToString() { return $"{Name} v{Version} - {Guid}"; } - - internal void ApplyToEntity() - { - PluginEntity.Id = Guid; - PluginEntity.IsEnabled = Enabled; - } - - internal void CreateLockFile() - { - File.Create(Path.Combine(Directory.FullName, "artemis.lock")).Close(); - } - - internal void DeleteLockFile() - { - if (GetLockFileCreated()) - File.Delete(Path.Combine(Directory.FullName, "artemis.lock")); - } - - internal bool GetLockFileCreated() - { - return File.Exists(Path.Combine(Directory.FullName, "artemis.lock")); - } - - public string? ResolveRelativePath(string path) - { - return path == null ? null : Path.Combine(Directory.FullName, path); - } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Settings/PluginSetting.cs b/src/Artemis.Core/Plugins/Settings/PluginSetting.cs index f3b068f83..d8738dd70 100644 --- a/src/Artemis.Core/Plugins/Settings/PluginSetting.cs +++ b/src/Artemis.Core/Plugins/Settings/PluginSetting.cs @@ -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 /// /// The value type of the setting - public class PluginSetting : PropertyChangedBase + public class PluginSetting : 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(); diff --git a/src/Artemis.Core/Plugins/Settings/PluginSettings.cs b/src/Artemis.Core/Plugins/Settings/PluginSettings.cs index f47f50a68..65a176e2c 100644 --- a/src/Artemis.Core/Plugins/Settings/PluginSettings.cs +++ b/src/Artemis.Core/Plugins/Settings/PluginSettings.cs @@ -14,17 +14,17 @@ namespace Artemis.Core private readonly IPluginRepository _pluginRepository; private readonly Dictionary _settingEntities; - internal PluginSettings(PluginInfo pluginInfo, IPluginRepository pluginRepository) + internal PluginSettings(Plugin plugin, IPluginRepository pluginRepository) { - PluginInfo = pluginInfo; + Plugin = plugin; _pluginRepository = pluginRepository; _settingEntities = new Dictionary(); } /// - /// Gets the info of the plugin this setting belongs to + /// Gets the plugin these settings belong to /// - public PluginInfo PluginInfo { get; } + public Plugin Plugin { get; } /// /// 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) _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 pluginSetting = new PluginSetting(PluginInfo, _pluginRepository, settingEntity); + PluginSetting pluginSetting = new PluginSetting(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 diff --git a/src/Artemis.Core/Plugins/PluginUpdateRegistration.cs b/src/Artemis.Core/Plugins/TimedUpdateRegistration.cs similarity index 66% rename from src/Artemis.Core/Plugins/PluginUpdateRegistration.cs rename to src/Artemis.Core/Plugins/TimedUpdateRegistration.cs index 9dcc035ca..2b5720296 100644 --- a/src/Artemis.Core/Plugins/PluginUpdateRegistration.cs +++ b/src/Artemis.Core/Plugins/TimedUpdateRegistration.cs @@ -8,40 +8,41 @@ namespace Artemis.Core /// /// Represents a registration for a timed plugin update /// - public class PluginUpdateRegistration + public class TimedUpdateRegistration : IDisposable { private DateTime _lastEvent; private Timer _timer; + private bool _disposed; - internal PluginUpdateRegistration(PluginInfo pluginInfo, TimeSpan interval, Action action) + internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Action action) { - PluginInfo = pluginInfo; + Feature = feature; Interval = interval; Action = action; - PluginInfo.Instance.PluginEnabled += InstanceOnPluginEnabled; - PluginInfo.Instance.PluginDisabled += InstanceOnPluginDisabled; - if (PluginInfo.Instance.Enabled) + Feature.Enabled += FeatureOnEnabled; + Feature.Disabled += FeatureOnDisabled; + if (Feature.IsEnabled) Start(); } - internal PluginUpdateRegistration(PluginInfo pluginInfo, TimeSpan interval, Func asyncAction) + internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Func asyncAction) { - PluginInfo = pluginInfo; + Feature = feature; Interval = interval; AsyncAction = asyncAction; - - PluginInfo.Instance.PluginEnabled += InstanceOnPluginEnabled; - PluginInfo.Instance.PluginDisabled += InstanceOnPluginDisabled; - if (PluginInfo.Instance.Enabled) + + Feature.Enabled += FeatureOnEnabled; + Feature.Disabled += FeatureOnDisabled; + if (Feature.IsEnabled) Start(); } /// - /// Gets the plugin info of the plugin this registration is associated with + /// Gets the plugin feature this registration is associated with /// - public PluginInfo PluginInfo { get; } + public PluginFeature Feature { get; } /// /// Gets the interval at which the update should occur @@ -64,10 +65,13 @@ namespace Artemis.Core /// public void Start() { + if (_disposed) + throw new ObjectDisposedException("TimedUpdateRegistration"); + lock (this) { - if (!PluginInfo.Instance.Enabled) - 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 /// 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.Instance.Enabled) + 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.Instance 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 InstanceOnPluginEnabled(object sender, EventArgs e) + private void FeatureOnEnabled(object sender, EventArgs e) { Start(); } - private void InstanceOnPluginDisabled(object sender, EventArgs e) + private void FeatureOnDisabled(object sender, EventArgs e) { Stop(); } + + /// + public void Dispose() + { + Stop(); + + Feature.Enabled -= FeatureOnEnabled; + Feature.Disabled -= FeatureOnDisabled; + + _disposed = true; + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 7cb158c7e..a3ab25712 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -28,7 +28,7 @@ namespace Artemis.Core.Services private readonly Stopwatch _frameStopWatch; private readonly ILogger _logger; private readonly PluginSetting _loggingLevel; - private readonly IPluginService _pluginService; + private readonly IPluginManagementService _pluginManagementService; private readonly IProfileService _profileService; private readonly IRgbService _rgbService; private readonly ISurfaceService _surfaceService; @@ -36,14 +36,14 @@ namespace Artemis.Core.Services private List _modules; // ReSharper disable once UnusedParameter.Local - Storage migration service is injected early to ensure it runs before anything else - public CoreService(IKernel kernel, ILogger logger, StorageMigrationService _, ISettingsService settingsService, IPluginService pluginService, + public CoreService(IKernel kernel, ILogger logger, StorageMigrationService _, ISettingsService settingsService, IPluginManagementService pluginManagementService, IRgbService rgbService, ISurfaceService surfaceService, IProfileService profileService, IModuleService moduleService) { Kernel = kernel; - Constants.CorePluginInfo.Kernel = kernel; + Constants.CorePlugin.Kernel = kernel; _logger = logger; - _pluginService = pluginService; + _pluginManagementService = pluginManagementService; _rgbService = rgbService; _surfaceService = surfaceService; _profileService = profileService; @@ -57,8 +57,8 @@ namespace Artemis.Core.Services _rgbService.Surface.Updated += SurfaceOnUpdated; _loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel(); - _pluginService.PluginEnabled += (sender, args) => UpdatePluginCache(); - _pluginService.PluginDisabled += (sender, args) => UpdatePluginCache(); + _pluginManagementService.PluginEnabled += (sender, args) => UpdatePluginCache(); + _pluginManagementService.PluginDisabled += (sender, args) => UpdatePluginCache(); } public TimeSpan FrameTime { get; private set; } @@ -68,7 +68,7 @@ namespace Artemis.Core.Services public void Dispose() { // Dispose services - _pluginService.Dispose(); + _pluginManagementService.Dispose(); } public bool IsInitialized { get; set; } @@ -85,8 +85,8 @@ namespace Artemis.Core.Services DeserializationLogger.Initialize(Kernel); // Initialize the services - _pluginService.CopyBuiltInPlugins(); - _pluginService.LoadPlugins(StartupArguments.Contains("--ignore-plugin-lock")); + _pluginManagementService.CopyBuiltInPlugins(); + _pluginManagementService.LoadPlugins(StartupArguments.Contains("--ignore-plugin-lock")); ArtemisSurface surfaceConfig = _surfaceService.ActiveSurface; if (surfaceConfig != null) @@ -133,8 +133,8 @@ namespace Artemis.Core.Services private void UpdatePluginCache() { - _modules = _pluginService.GetPluginsOfType().Where(p => p.Enabled).ToList(); - _dataModelExpansions = _pluginService.GetPluginsOfType().Where(p => p.Enabled).ToList(); + _modules = _pluginManagementService.GetFeaturesOfType().Where(p => p.IsEnabled).ToList(); + _dataModelExpansions = _pluginManagementService.GetFeaturesOfType().Where(p => p.IsEnabled).ToList(); } private void ConfigureJsonConvert() @@ -162,8 +162,8 @@ namespace Artemis.Core.Services lock (_dataModelExpansions) { // Update all active modules, check Enabled status because it may go false before before the _dataModelExpansions list is updated - foreach (BaseDataModelExpansion dataModelExpansion in _dataModelExpansions.Where(e => e.Enabled)) - dataModelExpansion.Update(args.DeltaTime); + foreach (BaseDataModelExpansion dataModelExpansion in _dataModelExpansions.Where(e => e.IsEnabled)) + dataModelExpansion.InternalUpdate(args.DeltaTime); } List modules; diff --git a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs new file mode 100644 index 000000000..c85eba6b3 --- /dev/null +++ b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using Artemis.Core.DeviceProviders; +using RGB.NET.Core; + +namespace Artemis.Core.Services +{ + /// + /// A service providing plugin management + /// + 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) + /// + void CopyBuiltInPlugins(); + + /// + /// Loads all installed plugins. If plugins already loaded this will reload them all + /// + void LoadPlugins(bool ignorePluginLock); + + /// + /// Unloads all installed plugins. + /// + void UnloadPlugins(); + + /// + /// Loads the plugin located in the provided + /// + /// The directory where the plugin is located + Plugin LoadPlugin(DirectoryInfo directory); + + /// + /// Enables the provided + /// + /// The plugin to enable + /// Whether or not to save the new enabled state + void EnablePlugin(Plugin plugin, bool saveState, bool ignorePluginLock = false); + + /// + /// Unloads the provided + /// + /// The plugin to unload + void UnloadPlugin(Plugin plugin); + + /// + /// Disables the provided + /// + /// The plugin to disable + /// Whether or not to save the new enabled state + void DisablePlugin(Plugin plugin, bool saveState); + + /// + /// Enables the provided plugin feature + /// + /// The feature to enable + /// Whether or not to save the new enabled state + /// If true, fails if there is a lock file present + void EnablePluginFeature(PluginFeature pluginFeature, bool saveState, bool isAutoEnable = false); + + /// + /// Disables the provided plugin feature + /// + /// The feature to enable + /// Whether or not to save the new enabled state + void DisablePluginFeature(PluginFeature pluginFeature, bool saveState); + + /// + /// Gets the plugin info of all loaded plugins + /// + /// A list containing all the plugin info + List GetAllPlugins(); + + /// + /// Finds all enabled instances of + /// + /// + /// Either or a plugin type implementing + /// + /// + /// Returns a list of feature instances of + List GetFeaturesOfType() where T : PluginFeature; + + /// + /// Gets the plugin that provided the specified assembly + /// + /// + /// + Plugin GetPluginByAssembly(Assembly assembly); + + /// + /// Returns the plugin info of the current call stack + /// + /// If the current call stack contains a plugin, the plugin. Otherwise null + Plugin? GetCallingPlugin(); + + /// + /// Gets the plugin that defined the specified device + /// + /// + /// + DeviceProvider GetDeviceProviderByDevice(IRGBDevice device); + + #region Events + + /// + /// Occurs when built-in plugins are being loaded + /// + event EventHandler CopyingBuildInPlugins; + + /// + /// Occurs when a plugin has started loading + /// + event EventHandler PluginLoading; + + /// + /// Occurs when a plugin has loaded + /// + event EventHandler PluginLoaded; + + /// + /// Occurs when a plugin has been unloaded + /// + event EventHandler PluginUnloaded; + + /// + /// Occurs when a plugin is being enabled + /// + event EventHandler PluginEnabling; + + /// + /// Occurs when a plugin has been enabled + /// + event EventHandler PluginEnabled; + + /// + /// Occurs when a plugin has been disabled + /// + event EventHandler PluginDisabled; + + /// + /// Occurs when a plugin feature is being enabled + /// + public event EventHandler PluginFeatureEnabling; + + /// + /// Occurs when a plugin feature has been enabled + /// + public event EventHandler PluginFeatureEnabled; + + /// + /// Occurs when a plugin feature could not be enabled + /// + public event EventHandler PluginFeatureEnableFailed; + + /// + /// 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/Interfaces/IPluginService.cs b/src/Artemis.Core/Services/Interfaces/IPluginService.cs index 0c8deca44..4ac1ced7c 100644 --- a/src/Artemis.Core/Services/Interfaces/IPluginService.cs +++ b/src/Artemis.Core/Services/Interfaces/IPluginService.cs @@ -1,138 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using RGB.NET.Core; - -namespace Artemis.Core.Services +namespace Artemis.Core.Services { /// - /// A service providing plugin management + /// An interface for services provided by plugins. + /// + /// Any service implementing this interface will be available inside the plugin as a singleton through dependency + /// injection + /// /// - public interface IPluginService : IArtemisService, IDisposable + public interface IPluginService { - /// - /// 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) - /// - void CopyBuiltInPlugins(); - - /// - /// Loads all installed plugins. If plugins already loaded this will reload them all - /// - void LoadPlugins(bool ignorePluginLock); - - /// - /// Unloads all installed plugins. - /// - void UnloadPlugins(); - - /// - /// Loads the plugin defined in the provided - /// - /// The plugin info defining the plugin to load - void LoadPlugin(PluginInfo pluginInfo); - - /// - /// Unloads the plugin defined in the provided - /// - /// The plugin info defining the plugin to unload - void UnloadPlugin(PluginInfo pluginInfo); - - /// - /// Enables the provided plugin - /// - /// - /// If true, fails if there is a lock file present - void EnablePlugin(Plugin plugin, bool isAutoEnable = false); - - /// - /// Disables the provided plugin - /// - /// - void DisablePlugin(Plugin plugin); - - /// - /// Finds the plugin info related to the plugin - /// - /// The plugin you want to find the plugin info for - /// The plugins PluginInfo - PluginInfo GetPluginInfo(Plugin plugin); - - /// - /// Gets the plugin info of all loaded plugins - /// - /// A list containing all the plugin info - List GetAllPluginInfo(); - - /// - /// Finds all enabled instances of - /// - /// Either or a plugin type implementing - /// Returns a list of plugin instances of - List GetPluginsOfType() where T : Plugin; - - /// - /// Gets the plugin that provided the specified assembly - /// - /// - /// - Plugin GetPluginByAssembly(Assembly assembly); - - /// - /// Gets the plugin that defined the specified device - /// - /// - /// - Plugin GetPluginByDevice(IRGBDevice device); - - /// - /// Returns the plugin info of the current call stack - /// - /// If the current call stack contains a plugin, the plugin. Otherwise null - Plugin GetCallingPlugin(); - - #region Events - - /// - /// Occurs when built-in plugins are being loaded - /// - event EventHandler CopyingBuildInPlugins; - - /// - /// Occurs when a plugin has started loading - /// - event EventHandler PluginLoading; - - /// - /// Occurs when a plugin has loaded - /// - event EventHandler PluginLoaded; - - /// - /// Occurs when a plugin has been unloaded - /// - event EventHandler PluginUnloaded; - - /// - /// Occurs when a plugin is being enabled - /// - event EventHandler PluginEnabling; - - /// - /// Occurs when a plugin has been enabled - /// - event EventHandler PluginEnabled; - - /// - /// Occurs when a plugin has been disabled - /// - event EventHandler PluginDisabled; - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/ModuleService.cs b/src/Artemis.Core/Services/ModuleService.cs index 1a7a2a428..97ccf53ce 100644 --- a/src/Artemis.Core/Services/ModuleService.cs +++ b/src/Artemis.Core/Services/ModuleService.cs @@ -17,25 +17,138 @@ namespace Artemis.Core.Services private static readonly SemaphoreSlim ActiveModuleSemaphore = new SemaphoreSlim(1, 1); private readonly ILogger _logger; private readonly IModuleRepository _moduleRepository; - private readonly IPluginService _pluginService; + private readonly IPluginManagementService _pluginManagementService; private readonly IProfileService _profileService; - public ModuleService(ILogger logger, IModuleRepository moduleRepository, IPluginService pluginService, IProfileService profileService) + public ModuleService(ILogger logger, IModuleRepository moduleRepository, IPluginManagementService pluginManagementService, IProfileService profileService) { _logger = logger; _moduleRepository = moduleRepository; - _pluginService = pluginService; + _pluginManagementService = pluginManagementService; _profileService = profileService; - _pluginService.PluginEnabled += PluginServiceOnPluginEnabled; + _pluginManagementService.PluginFeatureEnabled += OnPluginFeatureEnabled; Timer activationUpdateTimer = new Timer(2000); activationUpdateTimer.Start(); activationUpdateTimer.Elapsed += ActivationUpdateTimerOnElapsed; - foreach (Module module in _pluginService.GetPluginsOfType()) + foreach (Module module in _pluginManagementService.GetFeaturesOfType()) InitialiseOrApplyPriority(module); } + private async void ActivationUpdateTimerOnElapsed(object sender, ElapsedEventArgs e) + { + await UpdateModuleActivation(); + } + + private async Task ActivateModule(Module module) + { + try + { + module.Activate(false); + + // If this is a profile module, activate the last active profile after module activation + if (module is ProfileModule profileModule) + await _profileService.ActivateLastProfileAnimated(profileModule); + } + catch (Exception e) + { + _logger.Error(new ArtemisPluginFeatureException( + module, "Failed to activate module and last profile.", e), "Failed to activate module and last profile" + ); + throw; + } + } + + private async Task DeactivateModule(Module module) + { + try + { + // If this is a profile module, animate profile disable + // module.Deactivate would do the same but without animation + if (module.IsActivated && module is ProfileModule profileModule) + await profileModule.ChangeActiveProfileAnimated(null, null); + + module.Deactivate(false); + } + catch (Exception e) + { + _logger.Error(new ArtemisPluginFeatureException( + module, "Failed to deactivate module and last profile.", e), "Failed to deactivate module and last profile" + ); + throw; + } + } + + private void OverrideActivate(Module module) + { + try + { + if (module.IsActivated) + return; + + // If activating while it should be deactivated, its an override + bool shouldBeActivated = module.EvaluateActivationRequirements(); + module.Activate(!shouldBeActivated); + + // If this is a profile module, activate the last active profile after module activation + if (module is ProfileModule profileModule) + _profileService.ActivateLastProfile(profileModule); + } + catch (Exception e) + { + _logger.Error(new ArtemisPluginFeatureException( + module, "Failed to activate module and last profile.", e), "Failed to activate module and last profile" + ); + throw; + } + } + + private void OverrideDeactivate(Module module, bool clearingOverride) + { + try + { + if (!module.IsActivated) + return; + + // If deactivating while it should be activated, its an override + bool shouldBeActivated = module.EvaluateActivationRequirements(); + // No need to deactivate if it is not in an overridden state + if (shouldBeActivated && !module.IsActivatedOverride && !clearingOverride) + return; + + module.Deactivate(true); + } + catch (Exception e) + { + _logger.Error(new ArtemisPluginFeatureException( + module, "Failed to deactivate module and last profile.", e), "Failed to deactivate module and last profile" + ); + throw; + } + } + + private void OnPluginFeatureEnabled(object? sender, PluginFeatureEventArgs e) + { + if (e.PluginFeature is Module module) + InitialiseOrApplyPriority(module); + } + + private void InitialiseOrApplyPriority(Module module) + { + ModulePriorityCategory category = module.DefaultPriorityCategory; + int priority = 1; + + module.Entity = _moduleRepository.GetByModuleId(module.Id); + if (module.Entity != null) + { + category = (ModulePriorityCategory) module.Entity.PriorityCategory; + priority = module.Entity.Priority; + } + + UpdateModulePriority(module, category, priority); + } + public Module ActiveModuleOverride { get; private set; } public async Task SetActiveModuleOverride(Module overrideModule) @@ -46,17 +159,19 @@ namespace Artemis.Core.Services if (ActiveModuleOverride == overrideModule) return; - + if (overrideModule != null) { OverrideActivate(overrideModule); _logger.Information($"Setting active module override to {overrideModule.DisplayName}"); } else + { _logger.Information("Clearing active module override"); + } // Always deactivate all other modules whenever override is called - List modules = _pluginService.GetPluginsOfType().ToList(); + List modules = _pluginManagementService.GetFeaturesOfType().ToList(); foreach (Module module in modules.Where(m => m != overrideModule)) OverrideDeactivate(module, overrideModule != null); @@ -83,13 +198,8 @@ namespace Artemis.Core.Services // the principle is different for this service but not for the module bool shouldBeActivated = ActiveModuleOverride.EvaluateActivationRequirements(); if (shouldBeActivated && ActiveModuleOverride.IsActivatedOverride) - { ActiveModuleOverride.Reactivate(true, false); - } - else if (!shouldBeActivated && !ActiveModuleOverride.IsActivatedOverride) - { - ActiveModuleOverride.Reactivate(false, true); - } + else if (!shouldBeActivated && !ActiveModuleOverride.IsActivatedOverride) ActiveModuleOverride.Reactivate(false, true); return; } @@ -97,19 +207,17 @@ namespace Artemis.Core.Services Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); - List modules = _pluginService.GetPluginsOfType().ToList(); + List modules = _pluginManagementService.GetFeaturesOfType().ToList(); List tasks = new List(); foreach (Module module in modules) - { lock (module) { - bool shouldBeActivated = module.EvaluateActivationRequirements() && module.Enabled; + bool shouldBeActivated = module.EvaluateActivationRequirements() && module.IsEnabled; if (shouldBeActivated && !module.IsActivated) tasks.Add(ActivateModule(module)); else if (!shouldBeActivated && module.IsActivated) tasks.Add(DeactivateModule(module)); } - } await Task.WhenAll(tasks); @@ -128,7 +236,12 @@ namespace Artemis.Core.Services if (module.PriorityCategory == category && module.Priority == priority) return; - List modules = _pluginService.GetPluginsOfType().Where(m => m.PriorityCategory == category).OrderBy(m => m.Priority).ToList(); + List modules = _pluginManagementService + .GetFeaturesOfType() + .Where(m => m.PriorityCategory == category) + .OrderBy(m => m.Priority) + .ToList(); + if (modules.Contains(module)) modules.Remove(module); @@ -149,110 +262,5 @@ namespace Artemis.Core.Services } } } - - private async void ActivationUpdateTimerOnElapsed(object sender, ElapsedEventArgs e) - { - await UpdateModuleActivation(); - } - - private async Task ActivateModule(Module module) - { - try - { - module.Activate(false); - - // If this is a profile module, activate the last active profile after module activation - if (module is ProfileModule profileModule) - await _profileService.ActivateLastProfileAnimated(profileModule); - } - catch (Exception e) - { - _logger.Error(new ArtemisPluginException(module.PluginInfo, "Failed to activate module and last profile.", e), "Failed to activate module and last profile"); - throw; - } - } - - private async Task DeactivateModule(Module module) - { - try - { - // If this is a profile module, animate profile disable - // module.Deactivate would do the same but without animation - if (module.IsActivated && module is ProfileModule profileModule) - await profileModule.ChangeActiveProfileAnimated(null, null); - - module.Deactivate(false); - } - catch (Exception e) - { - _logger.Error(new ArtemisPluginException(module.PluginInfo, "Failed to deactivate module and last profile.", e), "Failed to deactivate module and last profile"); - throw; - } - } - - private void OverrideActivate(Module module) - { - try - { - if (module.IsActivated) - return; - - // If activating while it should be deactivated, its an override - bool shouldBeActivated = module.EvaluateActivationRequirements(); - module.Activate(!shouldBeActivated); - - // If this is a profile module, activate the last active profile after module activation - if (module is ProfileModule profileModule) - _profileService.ActivateLastProfile(profileModule); - } - catch (Exception e) - { - _logger.Error(new ArtemisPluginException(module.PluginInfo, "Failed to activate module and last profile.", e), "Failed to activate module and last profile"); - throw; - } - } - - private void OverrideDeactivate(Module module, bool clearingOverride) - { - try - { - if (!module.IsActivated) - return; - - // If deactivating while it should be activated, its an override - bool shouldBeActivated = module.EvaluateActivationRequirements(); - // No need to deactivate if it is not in an overridden state - if (shouldBeActivated && !module.IsActivatedOverride && !clearingOverride) - return; - - module.Deactivate(true); - } - catch (Exception e) - { - _logger.Error(new ArtemisPluginException(module.PluginInfo, "Failed to deactivate module and last profile.", e), "Failed to deactivate module and last profile"); - throw; - } - } - - private void PluginServiceOnPluginEnabled(object sender, PluginEventArgs e) - { - if (e.PluginInfo.Instance is Module module) - InitialiseOrApplyPriority(module); - } - - private void InitialiseOrApplyPriority(Module module) - { - ModulePriorityCategory category = module.DefaultPriorityCategory; - int priority = 1; - - module.Entity = _moduleRepository.GetByPluginGuid(module.PluginInfo.Guid); - if (module.Entity != null) - { - category = (ModulePriorityCategory) module.Entity.PriorityCategory; - priority = module.Entity.Priority; - } - - UpdateModulePriority(module, category, priority); - } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs new file mode 100644 index 000000000..fdf0869a1 --- /dev/null +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -0,0 +1,567 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Reflection; +using Artemis.Core.DeviceProviders; +using Artemis.Core.Ninject; +using Artemis.Storage.Entities.Plugins; +using Artemis.Storage.Repositories.Interfaces; +using McMaster.NETCore.Plugins; +using Newtonsoft.Json; +using Ninject; +using Ninject.Extensions.ChildKernel; +using Ninject.Parameters; +using RGB.NET.Core; +using Serilog; + +namespace Artemis.Core.Services +{ + /// + /// Provides access to plugin loading and unloading + /// + internal class PluginManagementService : IPluginManagementService + { + private readonly IKernel _kernel; + private readonly ILogger _logger; + private readonly IPluginRepository _pluginRepository; + private readonly List _plugins; + + public PluginManagementService(IKernel kernel, ILogger logger, IPluginRepository pluginRepository) + { + _kernel = kernel; + _logger = logger; + _pluginRepository = pluginRepository; + _plugins = new List(); + + // Ensure the plugins directory exists + if (!Directory.Exists(Constants.DataFolder + "plugins")) + Directory.CreateDirectory(Constants.DataFolder + "plugins"); + } + + private void CopyBuiltInPlugin(FileInfo zipFileInfo, ZipArchive zipArchive) + { + DirectoryInfo pluginDirectory = new DirectoryInfo(Path.Combine(Constants.DataFolder, "plugins", Path.GetFileNameWithoutExtension(zipFileInfo.Name))); + bool createLockFile = File.Exists(Path.Combine(pluginDirectory.FullName, "artemis.lock")); + + // Remove the old directory if it exists + if (Directory.Exists(pluginDirectory.FullName)) + pluginDirectory.DeleteRecursively(); + Directory.CreateDirectory(pluginDirectory.FullName); + + zipArchive.ExtractToDirectory(pluginDirectory.FullName, true); + if (createLockFile) + File.Create(Path.Combine(pluginDirectory.FullName, "artemis.lock")).Close(); + } + + public bool LoadingPlugins { get; private set; } + + #region Built in plugins + + public void CopyBuiltInPlugins() + { + OnCopyingBuildInPlugins(); + DirectoryInfo pluginDirectory = new DirectoryInfo(Path.Combine(Constants.DataFolder, "plugins")); + + // Iterate built-in plugins + DirectoryInfo builtInPluginDirectory = new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), "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")) + { + // Find the metadata file in the zip + using ZipArchive archive = ZipFile.OpenRead(zipFile.FullName); + ZipArchiveEntry? metaDataFileEntry = archive.GetEntry("plugin.json"); + if (metaDataFileEntry == null) + throw new ArtemisPluginException("Couldn't find a plugin.json in " + zipFile.FullName); + + using StreamReader reader = new StreamReader(metaDataFileEntry.Open()); + PluginInfo builtInPluginInfo = JsonConvert.DeserializeObject(reader.ReadToEnd()); + + // Find the matching plugin in the plugin folder + DirectoryInfo? match = pluginDirectory.EnumerateDirectories().FirstOrDefault(d => d.Name == Path.GetFileNameWithoutExtension(zipFile.Name)); + if (match == null) + { + CopyBuiltInPlugin(zipFile, archive); + } + else + { + string metadataFile = Path.Combine(match.FullName, "plugin.json"); + if (!File.Exists(metadataFile)) + { + _logger.Debug("Copying missing built-in plugin {builtInPluginInfo}", builtInPluginInfo); + CopyBuiltInPlugin(zipFile, archive); + } + else + { + try + { + // Compare versions, copy if the same when debugging + PluginInfo pluginInfo = JsonConvert.DeserializeObject(File.ReadAllText(metadataFile)); + + if (builtInPluginInfo.Version > pluginInfo.Version) + { + _logger.Debug("Copying updated built-in plugin from {pluginInfo} to {builtInPluginInfo}", pluginInfo, builtInPluginInfo); + CopyBuiltInPlugin(zipFile, archive); + } + } + catch (Exception e) + { + throw new ArtemisPluginException("Failed read plugin metadata needed to install built-in plugin", e); + } + } + } + } + } + + #endregion + + public List GetAllPlugins() + { + return new List(_plugins); + } + + public List GetFeaturesOfType() where T : PluginFeature + { + return _plugins.Where(p => p.IsEnabled).SelectMany(p => p.Features.Where(i => i.IsEnabled && i is T)).Cast().ToList(); + } + + public Plugin? GetPluginByAssembly(Assembly assembly) + { + return _plugins.FirstOrDefault(p => p.Assembly == assembly); + } + + // TODO: move to a more appropriate service + public DeviceProvider GetDeviceProviderByDevice(IRGBDevice rgbDevice) + { + return GetFeaturesOfType().First(d => d.RgbDeviceProvider.Devices != null && d.RgbDeviceProvider.Devices.Contains(rgbDevice)); + } + + public Plugin? GetCallingPlugin() + { + StackTrace stackTrace = new StackTrace(); // get call stack + StackFrame[] stackFrames = stackTrace.GetFrames(); // get method calls (frames) + + foreach (StackFrame stackFrame in stackFrames) + { + Assembly assembly = stackFrame.GetMethod().DeclaringType.Assembly; + Plugin plugin = GetPluginByAssembly(assembly); + if (plugin != null) + return plugin; + } + + return null; + } + + public void Dispose() + { + UnloadPlugins(); + } + + #region Plugins + + public void LoadPlugins(bool ignorePluginLock) + { + if (LoadingPlugins) + throw new ArtemisCoreException("Cannot load plugins while a previous load hasn't been completed yet."); + + lock (_plugins) + { + LoadingPlugins = true; + + // Unload all currently loaded plugins first + UnloadPlugins(); + + // Load the plugin assemblies into the plugin context + DirectoryInfo pluginDirectory = new DirectoryInfo(Path.Combine(Constants.DataFolder, "plugins")); + foreach (DirectoryInfo subDirectory in pluginDirectory.EnumerateDirectories()) + { + try + { + Plugin plugin = LoadPlugin(subDirectory); + if (plugin.Entity.IsEnabled) + EnablePlugin(plugin, false, ignorePluginLock); + } + catch (Exception e) + { + _logger.Warning(new ArtemisPluginException("Failed to load plugin", e), "Plugin exception"); + } + } + + LoadingPlugins = false; + } + } + + public void UnloadPlugins() + { + lock (_plugins) + { + // Unload all plugins + while (_plugins.Count > 0) + UnloadPlugin(_plugins[0]); + + _plugins.Clear(); + } + } + + public Plugin LoadPlugin(DirectoryInfo directory) + { + lock (_plugins) + { + _logger.Debug("Loading plugin from {directory}", directory.FullName); + + // Load the metadata + string metadataFile = Path.Combine(directory.FullName, "plugin.json"); + if (!File.Exists(metadataFile)) + _logger.Warning(new ArtemisPluginException("Couldn't find the plugins metadata file at " + metadataFile), "Plugin exception"); + + // PluginInfo contains the ID which we need to move on + PluginInfo pluginInfo = JsonConvert.DeserializeObject(File.ReadAllText(metadataFile)); + + if (pluginInfo.Guid == Constants.CorePluginInfo.Guid) + throw new ArtemisPluginException($"Plugin cannot use reserved GUID {pluginInfo.Guid}"); + + // Ensure the plugin is not already loaded + if (_plugins.Any(p => p.Guid == pluginInfo.Guid)) + throw new ArtemisCoreException("Cannot load a plugin that is already loaded"); + + Plugin plugin = new Plugin(pluginInfo, directory); + OnPluginLoading(new PluginEventArgs(plugin)); + + // Load the entity and fall back on creating a new one + plugin.Entity = _pluginRepository.GetPluginByGuid(pluginInfo.Guid) ?? new PluginEntity {Id = plugin.Guid, IsEnabled = true}; + + // Locate the main assembly entry + string? mainFile = plugin.ResolveRelativePath(plugin.Info.Main); + if (!File.Exists(mainFile)) + throw new ArtemisPluginException(plugin, "Couldn't find the plugins main entry at " + mainFile); + + // Load the plugin, all types implementing Plugin and register them with DI + plugin.PluginLoader = PluginLoader.CreateFromAssemblyFile(mainFile!, configure => + { + configure.IsUnloadable = true; + configure.PreferSharedTypes = true; + }); + + try + { + plugin.Assembly = plugin.PluginLoader.LoadDefaultAssembly(); + } + catch (Exception e) + { + throw new ArtemisPluginException(plugin, "Failed to load the plugins assembly", e); + } + + List bootstrappers = plugin.Assembly.GetTypes().Where(t => typeof(IPluginBootstrapper).IsAssignableFrom(t)).ToList(); + if (bootstrappers.Count > 1) + _logger.Warning($"{plugin} has more than one bootstrapper, only initializing {bootstrappers.First().FullName}"); + if (bootstrappers.Any()) + plugin.Bootstrapper = (IPluginBootstrapper?) Activator.CreateInstance(bootstrappers.First()); + + _plugins.Add(plugin); + + OnPluginLoaded(new PluginEventArgs(plugin)); + + return plugin; + } + } + + public void EnablePlugin(Plugin plugin, bool saveState, bool ignorePluginLock) + { + if (plugin.Assembly == null) + throw new ArtemisPluginException(plugin, "Cannot enable a plugin that hasn't successfully been loaded"); + + // Create the Ninject child kernel and load the module + plugin.Kernel = new ChildKernel(_kernel, new PluginModule(plugin)); + plugin.SetEnabled(true); + + // Get the Plugin feature from the main assembly and if there is only one, instantiate it + List featureTypes; + try + { + featureTypes = plugin.Assembly.GetTypes().Where(t => typeof(PluginFeature).IsAssignableFrom(t)).ToList(); + } + catch (ReflectionTypeLoadException e) + { + throw new ArtemisPluginException(plugin, "Failed to initialize the plugin assembly", new AggregateException(e.LoaderExceptions)); + } + + if (!featureTypes.Any()) + _logger.Warning("Plugin {plugin} contains no features", plugin); + + // Create instances of each feature and add them to the plugin + // Construction should be simple and not contain any logic so failure at this point means the entire plugin fails + foreach (Type featureType in featureTypes) + { + try + { + plugin.Kernel.Bind(featureType).ToSelf().InSingletonScope(); + + // Include Plugin as a parameter for the PluginSettingsProvider + IParameter[] parameters = {new Parameter("Plugin", plugin, false)}; + PluginFeature instance = (PluginFeature) plugin.Kernel.Get(featureType, parameters); + plugin.AddFeature(instance); + + // Load the enabled state and if not found, default to true + instance.Entity = plugin.Entity.Features.FirstOrDefault(i => i.Type == featureType.FullName) ?? + new PluginFeatureEntity {IsEnabled = true, Type = featureType.FullName}; + } + catch (Exception e) + { + throw new ArtemisPluginException(plugin, "Failed to instantiate feature", e); + } + } + + // Activate plugins after they are all loaded + foreach (PluginFeature pluginFeature in plugin.Features.Where(i => i.Entity.IsEnabled)) + { + try + { + EnablePluginFeature(pluginFeature, false, !ignorePluginLock); + } + catch (Exception) + { + // ignored, logged in EnablePluginFeature + } + } + + if (saveState) + { + plugin.Entity.IsEnabled = plugin.IsEnabled; + SavePlugin(plugin); + } + + OnPluginEnabled(new PluginEventArgs(plugin)); + } + + public void UnloadPlugin(Plugin plugin) + { + lock (_plugins) + { + try + { + DisablePlugin(plugin, false); + } + catch (Exception e) + { + _logger.Warning(new ArtemisPluginException(plugin, "Exception during DisablePlugin call for UnloadPlugin", e), "Failed to unload plugin"); + } + finally + { + OnPluginDisabled(new PluginEventArgs(plugin)); + } + + plugin.Dispose(); + _plugins.Remove(plugin); + } + } + + public void DisablePlugin(Plugin plugin, bool saveState) + { + if (!plugin.IsEnabled) + return; + + while (plugin.Features.Any()) + { + PluginFeature feature = plugin.Features[0]; + if (feature.IsEnabled) + DisablePluginFeature(feature, false); + plugin.RemoveFeature(feature); + } + + plugin.SetEnabled(false); + + plugin.Kernel.Dispose(); + plugin.Kernel = null; + + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + if (saveState) + { + plugin.Entity.IsEnabled = plugin.IsEnabled; + SavePlugin(plugin); + } + + OnPluginDisabled(new PluginEventArgs(plugin)); + } + + #endregion + + #region Features + + public void EnablePluginFeature(PluginFeature pluginFeature, bool saveState, bool isAutoEnable) + { + _logger.Debug("Enabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin); + + lock (_plugins) + { + OnPluginFeatureEnabling(new PluginFeatureEventArgs(pluginFeature)); + try + { + pluginFeature.SetEnabled(true, isAutoEnable); + if (saveState) + pluginFeature.Entity.IsEnabled = true; + } + catch (Exception e) + { + _logger.Warning( + new ArtemisPluginException(pluginFeature.Plugin, $"Exception during SetEnabled(true) on {pluginFeature}", e), + "Failed to enable plugin" + ); + throw; + } + finally + { + // On an auto-enable, ensure PluginInfo.Enabled is true even if enable failed, that way a failure on auto-enable does + // not affect the user's settings + if (saveState) + { + if (isAutoEnable) + pluginFeature.Entity.IsEnabled = true; + + SavePlugin(pluginFeature.Plugin); + } + + if (pluginFeature.IsEnabled) + { + _logger.Debug("Successfully enabled plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin); + OnPluginFeatureEnabled(new PluginFeatureEventArgs(pluginFeature)); + } + else + { + OnPluginFeatureEnableFailed(new PluginFeatureEventArgs(pluginFeature)); + } + } + } + } + + public void DisablePluginFeature(PluginFeature pluginFeature, bool saveState) + { + lock (_plugins) + { + try + { + _logger.Debug("Disabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin); + pluginFeature.SetEnabled(false); + } + finally + { + if (saveState) + { + pluginFeature.Entity.IsEnabled = false; + SavePlugin(pluginFeature.Plugin); + } + + if (!pluginFeature.IsEnabled) + { + _logger.Debug("Successfully disabled plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin); + OnPluginFeatureDisabled(new PluginFeatureEventArgs(pluginFeature)); + } + } + } + } + + #endregion + + #region Storage + + private void SavePlugin(Plugin plugin) + { + foreach (PluginFeature pluginFeature in plugin.Features) + { + if (plugin.Entity.Features.All(i => i.Type != pluginFeature.GetType().FullName)) + plugin.Entity.Features.Add(pluginFeature.Entity); + } + + _pluginRepository.SavePlugin(plugin.Entity); + } + + private PluginFeatureEntity GetOrCreateFeatureEntity(PluginFeature feature) + { + return feature.Plugin.Entity.Features.FirstOrDefault(i => i.Type == feature.GetType().FullName) ?? + new PluginFeatureEntity {IsEnabled = true, Type = feature.GetType().FullName}; + } + + #endregion + + #region Events + + public event EventHandler CopyingBuildInPlugins; + public event EventHandler PluginLoading; + public event EventHandler PluginLoaded; + public event EventHandler PluginUnloaded; + public event EventHandler PluginEnabling; + public event EventHandler PluginEnabled; + public event EventHandler PluginDisabled; + + public event EventHandler PluginFeatureEnabling; + public event EventHandler PluginFeatureEnabled; + public event EventHandler PluginFeatureDisabled; + public event EventHandler PluginFeatureEnableFailed; + + protected virtual void OnCopyingBuildInPlugins() + { + CopyingBuildInPlugins?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnPluginLoading(PluginEventArgs e) + { + PluginLoading?.Invoke(this, e); + } + + protected virtual void OnPluginLoaded(PluginEventArgs e) + { + PluginLoaded?.Invoke(this, e); + } + + protected virtual void OnPluginUnloaded(PluginEventArgs e) + { + PluginUnloaded?.Invoke(this, e); + } + + protected virtual void OnPluginEnabling(PluginEventArgs e) + { + PluginEnabling?.Invoke(this, e); + } + + protected virtual void OnPluginEnabled(PluginEventArgs e) + { + PluginEnabled?.Invoke(this, e); + } + + protected virtual void OnPluginDisabled(PluginEventArgs e) + { + PluginDisabled?.Invoke(this, e); + } + + protected virtual void OnPluginFeatureEnabling(PluginFeatureEventArgs e) + { + PluginFeatureEnabling?.Invoke(this, e); + } + + protected virtual void OnPluginFeatureEnabled(PluginFeatureEventArgs e) + { + PluginFeatureEnabled?.Invoke(this, e); + } + + protected virtual void OnPluginFeatureDisabled(PluginFeatureEventArgs e) + { + PluginFeatureDisabled?.Invoke(this, e); + } + + protected virtual void OnPluginFeatureEnableFailed(PluginFeatureEventArgs e) + { + PluginFeatureEnableFailed?.Invoke(this, e); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/PluginService.cs b/src/Artemis.Core/Services/PluginService.cs deleted file mode 100644 index ec583c2d8..000000000 --- a/src/Artemis.Core/Services/PluginService.cs +++ /dev/null @@ -1,446 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Reflection; -using Artemis.Core.DeviceProviders; -using Artemis.Core.Ninject; -using Artemis.Storage.Entities.Plugins; -using Artemis.Storage.Repositories.Interfaces; -using McMaster.NETCore.Plugins; -using Newtonsoft.Json; -using Ninject; -using Ninject.Extensions.ChildKernel; -using Ninject.Parameters; -using RGB.NET.Core; -using Serilog; - -namespace Artemis.Core.Services -{ - /// - /// Provides access to plugin loading and unloading - /// - internal class PluginService : IPluginService - { - private readonly IKernel _kernel; - private readonly ILogger _logger; - private readonly IPluginRepository _pluginRepository; - private readonly List _plugins; - - public PluginService(IKernel kernel, ILogger logger, IPluginRepository pluginRepository) - { - _kernel = kernel; - _logger = logger; - _pluginRepository = pluginRepository; - _plugins = new List(); - - // Ensure the plugins directory exists - if (!Directory.Exists(Constants.DataFolder + "plugins")) - Directory.CreateDirectory(Constants.DataFolder + "plugins"); - } - - public bool LoadingPlugins { get; private set; } - - public void CopyBuiltInPlugins() - { - OnCopyingBuildInPlugins(); - DirectoryInfo pluginDirectory = new DirectoryInfo(Path.Combine(Constants.DataFolder, "plugins")); - - // Iterate built-in plugins - DirectoryInfo builtInPluginDirectory = new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), "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")) - { - // Find the metadata file in the zip - using ZipArchive archive = ZipFile.OpenRead(zipFile.FullName); - ZipArchiveEntry? metaDataFileEntry = archive.GetEntry("plugin.json"); - if (metaDataFileEntry == null) - throw new ArtemisPluginException("Couldn't find a plugin.json in " + zipFile.FullName); - - using StreamReader reader = new StreamReader(metaDataFileEntry.Open()); - PluginInfo builtInPluginInfo = JsonConvert.DeserializeObject(reader.ReadToEnd()); - - // Find the matching plugin in the plugin folder - DirectoryInfo? match = pluginDirectory.EnumerateDirectories().FirstOrDefault(d => d.Name == Path.GetFileNameWithoutExtension(zipFile.Name)); - if (match == null) - CopyBuiltInPlugin(zipFile, archive); - else - { - string metadataFile = Path.Combine(match.FullName, "plugin.json"); - if (!File.Exists(metadataFile)) - { - _logger.Debug("Copying missing built-in plugin {builtInPluginInfo}", builtInPluginInfo); - CopyBuiltInPlugin(zipFile, archive); - } - else - { - try - { - // Compare versions, copy if the same when debugging - PluginInfo pluginInfo = JsonConvert.DeserializeObject(File.ReadAllText(metadataFile)); - - if (builtInPluginInfo.Version > pluginInfo.Version) - { - _logger.Debug("Copying updated built-in plugin from {pluginInfo} to {builtInPluginInfo}", pluginInfo, builtInPluginInfo); - CopyBuiltInPlugin(zipFile, archive); - } - } - catch (Exception e) - { - throw new ArtemisPluginException("Failed read plugin metadata needed to install built-in plugin", e); - } - } - } - } - } - - public void LoadPlugins(bool ignorePluginLock) - { - if (LoadingPlugins) - throw new ArtemisCoreException("Cannot load plugins while a previous load hasn't been completed yet."); - - lock (_plugins) - { - LoadingPlugins = true; - - // Unload all currently loaded plugins first - UnloadPlugins(); - - // Load the plugin assemblies into the plugin context - DirectoryInfo pluginDirectory = new DirectoryInfo(Path.Combine(Constants.DataFolder, "plugins")); - foreach (DirectoryInfo subDirectory in pluginDirectory.EnumerateDirectories()) - { - try - { - // Load the metadata - string metadataFile = Path.Combine(subDirectory.FullName, "plugin.json"); - if (!File.Exists(metadataFile)) _logger.Warning(new ArtemisPluginException("Couldn't find the plugins metadata file at " + metadataFile), "Plugin exception"); - - // Locate the main entry - PluginInfo pluginInfo = JsonConvert.DeserializeObject(File.ReadAllText(metadataFile)); - pluginInfo.Directory = subDirectory; - - LoadPlugin(pluginInfo); - } - catch (Exception e) - { - _logger.Warning(new ArtemisPluginException("Failed to load plugin", e), "Plugin exception"); - } - } - - // Activate plugins after they are all loaded - foreach (PluginInfo pluginInfo in _plugins.Where(p => p.Enabled)) - { - try - { - EnablePlugin(pluginInfo.Instance, !ignorePluginLock); - } - catch (Exception) - { - // ignored, logged in EnablePlugin - } - } - - LoadingPlugins = false; - } - } - - public void UnloadPlugins() - { - lock (_plugins) - { - // Unload all plugins - while (_plugins.Count > 0) - UnloadPlugin(_plugins[0]); - - _plugins.Clear(); - } - } - - public void LoadPlugin(PluginInfo pluginInfo) - { - lock (_plugins) - { - _logger.Debug("Loading plugin {pluginInfo}", pluginInfo); - OnPluginLoading(new PluginEventArgs(pluginInfo)); - - // Unload the plugin first if it is already loaded - if (_plugins.Contains(pluginInfo)) - UnloadPlugin(pluginInfo); - - PluginEntity pluginEntity = _pluginRepository.GetPluginByGuid(pluginInfo.Guid); - if (pluginEntity == null) - pluginEntity = new PluginEntity {Id = pluginInfo.Guid, IsEnabled = true}; - - pluginInfo.PluginEntity = pluginEntity; - pluginInfo.Enabled = pluginEntity.IsEnabled; - - string mainFile = Path.Combine(pluginInfo.Directory.FullName, pluginInfo.Main); - if (!File.Exists(mainFile)) - throw new ArtemisPluginException(pluginInfo, "Couldn't find the plugins main entry at " + mainFile); - - // Load the plugin, all types implementing Plugin and register them with DI - pluginInfo.PluginLoader = PluginLoader.CreateFromAssemblyFile(mainFile, configure => - { - configure.IsUnloadable = true; - configure.PreferSharedTypes = true; - }); - - try - { - pluginInfo.Assembly = pluginInfo.PluginLoader.LoadDefaultAssembly(); - } - catch (Exception e) - { - throw new ArtemisPluginException(pluginInfo, "Failed to load the plugins assembly", e); - } - - // Get the Plugin implementation from the main assembly and if there is only one, instantiate it - List pluginTypes; - try - { - pluginTypes = pluginInfo.Assembly.GetTypes().Where(t => typeof(Plugin).IsAssignableFrom(t)).ToList(); - } - catch (ReflectionTypeLoadException e) - { - throw new ArtemisPluginException(pluginInfo, "Failed to initialize the plugin assembly", new AggregateException(e.LoaderExceptions)); - } - - if (pluginTypes.Count > 1) - throw new ArtemisPluginException(pluginInfo, $"Plugin contains {pluginTypes.Count} implementations of Plugin, only 1 allowed"); - if (pluginTypes.Count == 0) - throw new ArtemisPluginException(pluginInfo, "Plugin contains no implementation of Plugin"); - - Type pluginType = pluginTypes.Single(); - try - { - IParameter[] parameters = { - new Parameter("PluginInfo", pluginInfo, false) - }; - pluginInfo.Kernel = new ChildKernel(_kernel); - pluginInfo.Kernel.Load(new PluginModule(pluginInfo)); - pluginInfo.Instance = (Plugin) pluginInfo.Kernel.Get(pluginType, constraint: null, parameters: parameters); - pluginInfo.Instance.PluginInfo = pluginInfo; - } - catch (Exception e) - { - throw new ArtemisPluginException(pluginInfo, "Failed to instantiate the plugin", e); - } - - _plugins.Add(pluginInfo); - OnPluginLoaded(new PluginEventArgs(pluginInfo)); - } - } - - public void UnloadPlugin(PluginInfo pluginInfo) - { - lock (_plugins) - { - try - { - pluginInfo.Instance.SetEnabled(false); - } - catch (Exception) - { - // TODO: Log these - } - finally - { - OnPluginDisabled(new PluginEventArgs(pluginInfo)); - } - - pluginInfo.Instance.Dispose(); - pluginInfo.PluginLoader.Dispose(); - pluginInfo.Kernel.Dispose(); - - _plugins.Remove(pluginInfo); - - OnPluginUnloaded(new PluginEventArgs(pluginInfo)); - } - } - - public void EnablePlugin(Plugin plugin, bool isAutoEnable = false) - { - _logger.Debug("Enabling plugin {pluginInfo}", plugin.PluginInfo); - OnPluginEnabling(new PluginEventArgs(plugin.PluginInfo)); - - lock (_plugins) - { - try - { - // A device provider may be queued for disable on next restart, this undoes that - if (plugin is DeviceProvider && plugin.Enabled && !plugin.PluginInfo.Enabled) - { - plugin.PluginInfo.Enabled = true; - plugin.PluginInfo.ApplyToEntity(); - _pluginRepository.SavePlugin(plugin.PluginInfo.PluginEntity); - return; - } - - plugin.SetEnabled(true, isAutoEnable); - } - catch (Exception e) - { - _logger.Warning(new ArtemisPluginException(plugin.PluginInfo, "Exception during SetEnabled(true)", e), "Failed to enable plugin"); - throw; - } - finally - { - // On an auto-enable, ensure PluginInfo.Enabled is true even if enable failed, that way a failure on auto-enable does - // not affect the user's settings - if (isAutoEnable) - plugin.PluginInfo.Enabled = true; - - plugin.PluginInfo.ApplyToEntity(); - _pluginRepository.SavePlugin(plugin.PluginInfo.PluginEntity); - - if (plugin.PluginInfo.Enabled) - _logger.Debug("Successfully enabled plugin {pluginInfo}", plugin.PluginInfo); - } - } - - OnPluginEnabled(new PluginEventArgs(plugin.PluginInfo)); - } - - public void DisablePlugin(Plugin plugin) - { - lock (_plugins) - { - _logger.Debug("Disabling plugin {pluginInfo}", plugin.PluginInfo); - - // Device providers cannot be disabled at runtime simply queue a disable for next restart - if (plugin is DeviceProvider) - { - // Don't call SetEnabled(false) but simply update enabled state and save it - plugin.PluginInfo.Enabled = false; - plugin.PluginInfo.ApplyToEntity(); - _pluginRepository.SavePlugin(plugin.PluginInfo.PluginEntity); - return; - } - - plugin.SetEnabled(false); - plugin.PluginInfo.ApplyToEntity(); - _pluginRepository.SavePlugin(plugin.PluginInfo.PluginEntity); - - _logger.Debug("Successfully disabled plugin {pluginInfo}", plugin.PluginInfo); - } - - OnPluginDisabled(new PluginEventArgs(plugin.PluginInfo)); - } - - public PluginInfo GetPluginInfo(Plugin plugin) - { - return _plugins.FirstOrDefault(p => p.Instance == plugin); - } - - public List GetAllPluginInfo() - { - return new List(_plugins); - } - - public List GetPluginsOfType() where T : Plugin - { - return _plugins.Where(p => p.Enabled && p.Instance is T).Select(p => (T) p.Instance).ToList(); - } - - public Plugin GetPluginByAssembly(Assembly assembly) - { - return _plugins.FirstOrDefault(p => p.Assembly == assembly)?.Instance; - } - - public Plugin GetPluginByDevice(IRGBDevice rgbDevice) - { - return GetPluginsOfType().First(d => d.RgbDeviceProvider.Devices != null && d.RgbDeviceProvider.Devices.Contains(rgbDevice)); - } - - public Plugin GetCallingPlugin() - { - StackTrace stackTrace = new StackTrace(); // get call stack - StackFrame[] stackFrames = stackTrace.GetFrames(); // get method calls (frames) - - foreach (StackFrame stackFrame in stackFrames) - { - Assembly assembly = stackFrame.GetMethod().DeclaringType.Assembly; - Plugin plugin = GetPluginByAssembly(assembly); - if (plugin != null) - return plugin; - } - - return null; - } - - public void Dispose() - { - UnloadPlugins(); - } - - private void CopyBuiltInPlugin(FileInfo zipFileInfo, ZipArchive zipArchive) - { - DirectoryInfo pluginDirectory = new DirectoryInfo(Path.Combine(Constants.DataFolder, "plugins", Path.GetFileNameWithoutExtension(zipFileInfo.Name))); - bool createLockFile = File.Exists(Path.Combine(pluginDirectory.FullName, "artemis.lock")); - - // Remove the old directory if it exists - if (Directory.Exists(pluginDirectory.FullName)) - pluginDirectory.DeleteRecursively(); - Directory.CreateDirectory(pluginDirectory.FullName); - - zipArchive.ExtractToDirectory(pluginDirectory.FullName, true); - if (createLockFile) - File.Create(Path.Combine(pluginDirectory.FullName, "artemis.lock")).Close(); - } - - #region Events - - public event EventHandler CopyingBuildInPlugins; - public event EventHandler PluginLoading; - public event EventHandler PluginLoaded; - public event EventHandler PluginUnloaded; - public event EventHandler PluginEnabling; - public event EventHandler PluginEnabled; - public event EventHandler PluginDisabled; - - protected virtual void OnCopyingBuildInPlugins() - { - CopyingBuildInPlugins?.Invoke(this, EventArgs.Empty); - } - - protected virtual void OnPluginLoading(PluginEventArgs e) - { - PluginLoading?.Invoke(this, e); - } - - protected virtual void OnPluginLoaded(PluginEventArgs e) - { - PluginLoaded?.Invoke(this, e); - } - - protected virtual void OnPluginUnloaded(PluginEventArgs e) - { - PluginUnloaded?.Invoke(this, e); - } - - protected virtual void OnPluginEnabling(PluginEventArgs e) - { - PluginEnabling?.Invoke(this, e); - } - - protected virtual void OnPluginEnabled(PluginEventArgs e) - { - PluginEnabled?.Invoke(this, e); - } - - protected virtual void OnPluginDisabled(PluginEventArgs e) - { - PluginDisabled?.Invoke(this, e); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Registration/ConditionOperatorService.cs b/src/Artemis.Core/Services/Registration/ConditionOperatorService.cs index a296efd4f..bc3664a4a 100644 --- a/src/Artemis.Core/Services/Registration/ConditionOperatorService.cs +++ b/src/Artemis.Core/Services/Registration/ConditionOperatorService.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Artemis.Core.DefaultTypes; namespace Artemis.Core.Services { @@ -12,14 +11,14 @@ namespace Artemis.Core.Services RegisterBuiltInConditionOperators(); } - public ConditionOperatorRegistration RegisterConditionOperator(PluginInfo pluginInfo, BaseConditionOperator conditionOperator) + public ConditionOperatorRegistration RegisterConditionOperator(Plugin plugin, BaseConditionOperator conditionOperator) { - if (pluginInfo == null) - throw new ArgumentNullException(nameof(pluginInfo)); + if (plugin == null) + throw new ArgumentNullException(nameof(plugin)); if (conditionOperator == null) throw new ArgumentNullException(nameof(conditionOperator)); - conditionOperator.PluginInfo = pluginInfo; + conditionOperator.Plugin = plugin; return ConditionOperatorStore.Add(conditionOperator); } @@ -43,30 +42,30 @@ namespace Artemis.Core.Services private void RegisterBuiltInConditionOperators() { // General usage for any type - RegisterConditionOperator(Constants.CorePluginInfo, new EqualsConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new NotEqualConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new EqualsConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new NotEqualConditionOperator()); // Numeric operators - RegisterConditionOperator(Constants.CorePluginInfo, new NumberEqualsConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new NumberNotEqualConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new LessThanConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new GreaterThanConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new LessThanOrEqualConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new GreaterThanOrEqualConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new NumberEqualsConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new NumberNotEqualConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new LessThanConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new GreaterThanConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new LessThanOrEqualConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new GreaterThanOrEqualConditionOperator()); // String operators - RegisterConditionOperator(Constants.CorePluginInfo, new StringEqualsConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new StringNotEqualConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new StringContainsConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new StringNotContainsConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new StringStartsWithConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new StringEndsWithConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new StringMatchesRegexConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new StringEqualsConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new StringNotEqualConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new StringContainsConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new StringNotContainsConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new StringStartsWithConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new StringEndsWithConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new StringMatchesRegexConditionOperator()); // Null checks, at the bottom // TODO: Implement a priority mechanism - RegisterConditionOperator(Constants.CorePluginInfo, new NullConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new NotNullConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new NullConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new NotNullConditionOperator()); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Registration/DataBindingService.cs b/src/Artemis.Core/Services/Registration/DataBindingService.cs index 48f853641..b800a528c 100644 --- a/src/Artemis.Core/Services/Registration/DataBindingService.cs +++ b/src/Artemis.Core/Services/Registration/DataBindingService.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Artemis.Core.DefaultTypes; namespace Artemis.Core.Services { @@ -12,14 +11,14 @@ namespace Artemis.Core.Services RegisterBuiltInModifiers(); } - public DataBindingModifierTypeRegistration RegisterModifierType(PluginInfo pluginInfo, BaseDataBindingModifierType dataBindingModifierType) + public DataBindingModifierTypeRegistration RegisterModifierType(Plugin plugin, BaseDataBindingModifierType dataBindingModifierType) { - if (pluginInfo == null) - throw new ArgumentNullException(nameof(pluginInfo)); + if (plugin == null) + throw new ArgumentNullException(nameof(plugin)); if (dataBindingModifierType == null) throw new ArgumentNullException(nameof(dataBindingModifierType)); - dataBindingModifierType.PluginInfo = pluginInfo; + dataBindingModifierType.Plugin = plugin; return DataBindingModifierTypeStore.Add(dataBindingModifierType); } @@ -43,41 +42,41 @@ namespace Artemis.Core.Services private void RegisterBuiltInModifiers() { // Numbers - General - RegisterModifierType(Constants.CorePluginInfo, new SumModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new SubtractModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new MultiplicationModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new DivideModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new PercentageOfModifierType()); + RegisterModifierType(Constants.CorePlugin, new SumModifierType()); + RegisterModifierType(Constants.CorePlugin, new SubtractModifierType()); + RegisterModifierType(Constants.CorePlugin, new MultiplicationModifierType()); + RegisterModifierType(Constants.CorePlugin, new DivideModifierType()); + RegisterModifierType(Constants.CorePlugin, new PercentageOfModifierType()); // Numbers - Advanced - RegisterModifierType(Constants.CorePluginInfo, new MaxModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new MinModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new ModuloModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new AbsoluteModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new PowerModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new SquareRootModifierType()); + RegisterModifierType(Constants.CorePlugin, new MaxModifierType()); + RegisterModifierType(Constants.CorePlugin, new MinModifierType()); + RegisterModifierType(Constants.CorePlugin, new ModuloModifierType()); + RegisterModifierType(Constants.CorePlugin, new AbsoluteModifierType()); + RegisterModifierType(Constants.CorePlugin, new PowerModifierType()); + RegisterModifierType(Constants.CorePlugin, new SquareRootModifierType()); // Numbers - Rounding - RegisterModifierType(Constants.CorePluginInfo, new FloorModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new RoundModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new CeilingModifierType()); + RegisterModifierType(Constants.CorePlugin, new FloorModifierType()); + RegisterModifierType(Constants.CorePlugin, new RoundModifierType()); + RegisterModifierType(Constants.CorePlugin, new CeilingModifierType()); // Numbers - Trigonometric - RegisterModifierType(Constants.CorePluginInfo, new SineModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new CosineModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new TangentModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new CotangentModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new SecantModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new CosecantModifierType()); + RegisterModifierType(Constants.CorePlugin, new SineModifierType()); + RegisterModifierType(Constants.CorePlugin, new CosineModifierType()); + RegisterModifierType(Constants.CorePlugin, new TangentModifierType()); + RegisterModifierType(Constants.CorePlugin, new CotangentModifierType()); + RegisterModifierType(Constants.CorePlugin, new SecantModifierType()); + RegisterModifierType(Constants.CorePlugin, new CosecantModifierType()); // Colors - RegisterModifierType(Constants.CorePluginInfo, new SKColorSumModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new SKColorSaturateModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new SKColorDesaturateModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new SKColorBrightenModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new SKColorDarkenModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new SKColorRotateHueModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new SKColorInvertModifierType()); + RegisterModifierType(Constants.CorePlugin, new SKColorSumModifierType()); + RegisterModifierType(Constants.CorePlugin, new SKColorSaturateModifierType()); + RegisterModifierType(Constants.CorePlugin, new SKColorDesaturateModifierType()); + RegisterModifierType(Constants.CorePlugin, new SKColorBrightenModifierType()); + RegisterModifierType(Constants.CorePlugin, new SKColorDarkenModifierType()); + RegisterModifierType(Constants.CorePlugin, new SKColorRotateHueModifierType()); + RegisterModifierType(Constants.CorePlugin, new SKColorInvertModifierType()); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Registration/DataModelService.cs b/src/Artemis.Core/Services/Registration/DataModelService.cs index 58b57ac07..b583439be 100644 --- a/src/Artemis.Core/Services/Registration/DataModelService.cs +++ b/src/Artemis.Core/Services/Registration/DataModelService.cs @@ -8,18 +8,19 @@ namespace Artemis.Core.Services { internal class DataModelService : IDataModelService { - public DataModelService(IPluginService pluginService) + public DataModelService(IPluginManagementService pluginManagementService) { // Add data models of already loaded plugins - foreach (Module module in pluginService.GetPluginsOfType().Where(p => p.Enabled)) + foreach (Module module in pluginManagementService.GetFeaturesOfType().Where(p => p.IsEnabled)) AddModuleDataModel(module); - foreach (BaseDataModelExpansion dataModelExpansion in pluginService.GetPluginsOfType().Where(p => p.Enabled)) + foreach (BaseDataModelExpansion dataModelExpansion in pluginManagementService.GetFeaturesOfType().Where(p => p.IsEnabled)) AddDataModelExpansionDataModel(dataModelExpansion); // Add data models of new plugins when they get enabled - pluginService.PluginEnabled += PluginServiceOnPluginEnabled; + pluginManagementService.PluginFeatureEnabled += OnPluginFeatureEnabled; } + public DataModelRegistration RegisterDataModel(DataModel dataModel) { if (dataModel == null) @@ -44,21 +45,16 @@ namespace Artemis.Core.Services return (T) DataModelStore.GetAll().FirstOrDefault(d => d.DataModel is T)?.DataModel; } - public DataModel GetPluginDataModel(Plugin plugin) + public DataModel? GetPluginDataModel(PluginFeature pluginFeature) { - return DataModelStore.Get(plugin.PluginInfo.Guid)?.DataModel; + return DataModelStore.Get(pluginFeature.Id)?.DataModel; } - - public DataModel GetPluginDataModel(Guid pluginGuid) + + private void OnPluginFeatureEnabled(object? sender, PluginFeatureEventArgs e) { - return DataModelStore.Get(pluginGuid)?.DataModel; - } - - private void PluginServiceOnPluginEnabled(object sender, PluginEventArgs e) - { - if (e.PluginInfo.Instance is Module module) + if (e.PluginFeature is Module module) AddModuleDataModel(module); - else if (e.PluginInfo.Instance is BaseDataModelExpansion dataModelExpansion) + else if (e.PluginFeature is BaseDataModelExpansion dataModelExpansion) AddDataModelExpansionDataModel(dataModelExpansion); } @@ -68,7 +64,7 @@ namespace Artemis.Core.Services return; if (module.InternalDataModel.DataModelDescription == null) - throw new ArtemisPluginException(module.PluginInfo, "Module overrides GetDataModelDescription but returned null"); + throw new ArtemisPluginFeatureException(module, "Module overrides GetDataModelDescription but returned null"); module.InternalDataModel.IsExpansion = module.InternalExpandsMainDataModel; RegisterDataModel(module.InternalDataModel); @@ -77,7 +73,7 @@ namespace Artemis.Core.Services private void AddDataModelExpansionDataModel(BaseDataModelExpansion dataModelExpansion) { if (dataModelExpansion.InternalDataModel.DataModelDescription == null) - throw new ArtemisPluginException(dataModelExpansion.PluginInfo, "Data model expansion overrides GetDataModelDescription but returned null"); + throw new ArtemisPluginFeatureException(dataModelExpansion, "Data model expansion overrides GetDataModelDescription but returned null"); dataModelExpansion.InternalDataModel.IsExpansion = true; RegisterDataModel(dataModelExpansion.InternalDataModel); diff --git a/src/Artemis.Core/Services/Registration/Interfaces/IConditionOperatorService.cs b/src/Artemis.Core/Services/Registration/Interfaces/IConditionOperatorService.cs index 49cf60b16..359fec8d9 100644 --- a/src/Artemis.Core/Services/Registration/Interfaces/IConditionOperatorService.cs +++ b/src/Artemis.Core/Services/Registration/Interfaces/IConditionOperatorService.cs @@ -12,9 +12,9 @@ namespace Artemis.Core.Services /// /// Registers a new condition operator for use in layer conditions /// - /// The PluginInfo of the plugin this condition operator belongs to + /// The plugin this condition operator belongs to /// The condition operator to register - ConditionOperatorRegistration RegisterConditionOperator([NotNull] PluginInfo pluginInfo, [NotNull] BaseConditionOperator conditionOperator); + ConditionOperatorRegistration RegisterConditionOperator([NotNull] Plugin plugin, [NotNull] BaseConditionOperator conditionOperator); /// /// Removes a condition operator so it is no longer available for use in layer conditions diff --git a/src/Artemis.Core/Services/Registration/Interfaces/IDataBindingService.cs b/src/Artemis.Core/Services/Registration/Interfaces/IDataBindingService.cs index bf33b1867..27ca01ab3 100644 --- a/src/Artemis.Core/Services/Registration/Interfaces/IDataBindingService.cs +++ b/src/Artemis.Core/Services/Registration/Interfaces/IDataBindingService.cs @@ -12,9 +12,9 @@ namespace Artemis.Core.Services /// /// Registers a new modifier type for use in data bindings /// - /// The PluginInfo of the plugin this modifier type belongs to + /// The plugin this modifier type belongs to /// The modifier type to register - DataBindingModifierTypeRegistration RegisterModifierType([NotNull] PluginInfo pluginInfo, [NotNull] BaseDataBindingModifierType dataBindingModifierType); + DataBindingModifierTypeRegistration RegisterModifierType([NotNull] Plugin plugin, [NotNull] BaseDataBindingModifierType dataBindingModifierType); /// /// Removes a modifier type so it is no longer available for use in data bindings diff --git a/src/Artemis.Core/Services/Registration/Interfaces/IDataModelService.cs b/src/Artemis.Core/Services/Registration/Interfaces/IDataModelService.cs index c9ab8a279..2f07006b6 100644 --- a/src/Artemis.Core/Services/Registration/Interfaces/IDataModelService.cs +++ b/src/Artemis.Core/Services/Registration/Interfaces/IDataModelService.cs @@ -34,13 +34,7 @@ namespace Artemis.Core.Services /// /// If found, returns the data model of the provided plugin /// - /// The plugin to find the data model of - DataModel GetPluginDataModel(Plugin plugin); - - /// - /// If found, returns the data model of the provided plugin GUID - /// - /// The GUID of the plugin to find the data model of - DataModel GetPluginDataModel(Guid pluginGuid); + /// The plugin to find the data model of + DataModel? GetPluginDataModel(PluginFeature pluginFeature); } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Registration/Interfaces/ILayerBrushService.cs b/src/Artemis.Core/Services/Registration/Interfaces/ILayerBrushService.cs index a8e576312..d0f6167e4 100644 --- a/src/Artemis.Core/Services/Registration/Interfaces/ILayerBrushService.cs +++ b/src/Artemis.Core/Services/Registration/Interfaces/ILayerBrushService.cs @@ -26,6 +26,6 @@ namespace Artemis.Core.Services /// /// Returns the descriptor of the default layer brush /// - LayerBrushDescriptor GetDefaultLayerBrush(); + LayerBrushDescriptor? GetDefaultLayerBrush(); } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Registration/LayerBrushService.cs b/src/Artemis.Core/Services/Registration/LayerBrushService.cs index 0ddca99ec..de738d6c4 100644 --- a/src/Artemis.Core/Services/Registration/LayerBrushService.cs +++ b/src/Artemis.Core/Services/Registration/LayerBrushService.cs @@ -38,11 +38,11 @@ namespace Artemis.Core.Services { PluginSetting defaultReference = _settingsService.GetSetting("ProfileEditor.DefaultLayerBrushDescriptor", new LayerBrushReference { - BrushPluginGuid = Guid.Parse("92a9d6ba-6f7a-4937-94d5-c1d715b4141a"), + LayerBrushProviderId = "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba", BrushType = "ColorBrush" }); - return LayerBrushStore.Get(defaultReference.Value.BrushPluginGuid, defaultReference.Value.BrushType)?.LayerBrushDescriptor; + return LayerBrushStore.Get(defaultReference.Value.LayerBrushProviderId, defaultReference.Value.BrushType)?.LayerBrushDescriptor; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/SettingsService.cs b/src/Artemis.Core/Services/SettingsService.cs index 24b267e63..a5ca7d658 100644 --- a/src/Artemis.Core/Services/SettingsService.cs +++ b/src/Artemis.Core/Services/SettingsService.cs @@ -7,9 +7,9 @@ namespace Artemis.Core.Services { private readonly PluginSettings _pluginSettings; - internal SettingsService(IPluginRepository pluginRepository) + public SettingsService(IPluginRepository pluginRepository) { - _pluginSettings = new PluginSettings(Constants.CorePluginInfo, pluginRepository); + _pluginSettings = new PluginSettings(Constants.CorePlugin, pluginRepository); } public PluginSetting GetSetting(string name, T defaultValue = default) diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 880ff1ab1..6ff5a49fd 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -13,19 +13,19 @@ namespace Artemis.Core.Services internal class ProfileService : IProfileService { private readonly ILogger _logger; - private readonly IPluginService _pluginService; + private readonly IPluginManagementService _pluginManagementService; private readonly IProfileRepository _profileRepository; private readonly ISurfaceService _surfaceService; - internal ProfileService(ILogger logger, - IPluginService pluginService, + public ProfileService(ILogger logger, + IPluginManagementService pluginManagementService, ISurfaceService surfaceService, IConditionOperatorService conditionOperatorService, IDataBindingService dataBindingService, IProfileRepository profileRepository) { _logger = logger; - _pluginService = pluginService; + _pluginManagementService = pluginManagementService; _surfaceService = surfaceService; _profileRepository = profileRepository; @@ -38,13 +38,13 @@ namespace Artemis.Core.Services public List GetProfileDescriptors(ProfileModule module) { - List profileEntities = _profileRepository.GetByPluginGuid(module.PluginInfo.Guid); + List profileEntities = _profileRepository.GetByModuleId(module.Id); return profileEntities.Select(e => new ProfileDescriptor(module, e)).ToList(); } public ProfileDescriptor CreateProfileDescriptor(ProfileModule module, string name) { - ProfileEntity profileEntity = new ProfileEntity {Id = Guid.NewGuid(), Name = name, PluginGuid = module.PluginInfo.Guid}; + ProfileEntity profileEntity = new ProfileEntity {Id = Guid.NewGuid(), Name = name, ModuleId = module.Id}; _profileRepository.Add(profileEntity); return new ProfileDescriptor(module, profileEntity); @@ -118,15 +118,15 @@ namespace Artemis.Core.Services } // This could happen during activation so subscribe to it - _pluginService.PluginEnabled += ActivatingProfilePluginToggle; - _pluginService.PluginDisabled += ActivatingProfilePluginToggle; + _pluginManagementService.PluginEnabled += ActivatingProfilePluginToggle; + _pluginManagementService.PluginDisabled += ActivatingProfilePluginToggle; _surfaceService.SurfaceConfigurationUpdated += ActivatingProfileSurfaceUpdate; await profileDescriptor.ProfileModule.ChangeActiveProfileAnimated(profile, _surfaceService.ActiveSurface); SaveActiveProfile(profileDescriptor.ProfileModule); - _pluginService.PluginEnabled -= ActivatingProfilePluginToggle; - _pluginService.PluginDisabled -= ActivatingProfilePluginToggle; + _pluginManagementService.PluginEnabled -= ActivatingProfilePluginToggle; + _pluginManagementService.PluginDisabled -= ActivatingProfilePluginToggle; _surfaceService.SurfaceConfigurationUpdated -= ActivatingProfileSurfaceUpdate; return profile; @@ -258,7 +258,7 @@ namespace Artemis.Core.Services public ProfileDescriptor GetLastActiveProfile(ProfileModule module) { - List moduleProfiles = _profileRepository.GetByPluginGuid(module.PluginInfo.Guid); + List moduleProfiles = _profileRepository.GetByModuleId(module.Id); if (!moduleProfiles.Any()) return CreateProfileDescriptor(module, "Default"); @@ -271,7 +271,7 @@ namespace Artemis.Core.Services if (module.ActiveProfile == null) return; - List profileEntities = _profileRepository.GetByPluginGuid(module.PluginInfo.Guid); + List profileEntities = _profileRepository.GetByModuleId(module.Id); foreach (ProfileEntity profileEntity in profileEntities) { profileEntity.IsActive = module.ActiveProfile.EntityId == profileEntity.Id; @@ -285,7 +285,7 @@ namespace Artemis.Core.Services /// private void ActiveProfilesPopulateLeds(ArtemisSurface surface) { - List profileModules = _pluginService.GetPluginsOfType(); + List profileModules = _pluginManagementService.GetFeaturesOfType(); foreach (ProfileModule profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList()) profileModule.ActiveProfile.PopulateLeds(surface); } diff --git a/src/Artemis.Core/Services/Storage/SurfaceService.cs b/src/Artemis.Core/Services/Storage/SurfaceService.cs index 4b6a97880..6b1fc1861 100644 --- a/src/Artemis.Core/Services/Storage/SurfaceService.cs +++ b/src/Artemis.Core/Services/Storage/SurfaceService.cs @@ -12,18 +12,18 @@ namespace Artemis.Core.Services internal class SurfaceService : ISurfaceService { private readonly ILogger _logger; - private readonly IPluginService _pluginService; + private readonly IPluginManagementService _pluginManagementService; private readonly PluginSetting _renderScaleSetting; private readonly IRgbService _rgbService; private readonly List _surfaceConfigurations; private readonly ISurfaceRepository _surfaceRepository; - public SurfaceService(ILogger logger, ISurfaceRepository surfaceRepository, IRgbService rgbService, IPluginService pluginService, ISettingsService settingsService) + public SurfaceService(ILogger logger, ISurfaceRepository surfaceRepository, IRgbService rgbService, IPluginManagementService pluginManagementService, ISettingsService settingsService) { _logger = logger; _surfaceRepository = surfaceRepository; _rgbService = rgbService; - _pluginService = pluginService; + _pluginManagementService = pluginManagementService; _surfaceConfigurations = new List(); _renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.5); @@ -44,8 +44,8 @@ namespace Artemis.Core.Services // Add all current devices foreach (IRGBDevice rgbDevice in _rgbService.LoadedDevices) { - Plugin plugin = _pluginService.GetPluginByDevice(rgbDevice); - configuration.Devices.Add(new ArtemisDevice(rgbDevice, plugin, configuration)); + PluginFeature pluginFeature = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice); + configuration.Devices.Add(new ArtemisDevice(rgbDevice, pluginFeature, configuration)); } lock (_surfaceConfigurations) @@ -136,8 +136,8 @@ namespace Artemis.Core.Services IRGBDevice device = _rgbService.Surface.Devices.FirstOrDefault(d => d.GetDeviceIdentifier() == position.DeviceIdentifier); if (device != null) { - Plugin plugin = _pluginService.GetPluginByDevice(device); - surfaceConfiguration.Devices.Add(new ArtemisDevice(device, plugin, surfaceConfiguration, position)); + PluginFeature pluginFeature = _pluginManagementService.GetDeviceProviderByDevice(device); + surfaceConfiguration.Devices.Add(new ArtemisDevice(device, pluginFeature, surfaceConfiguration, position)); } } @@ -178,8 +178,8 @@ namespace Artemis.Core.Services DeviceEntity existingDeviceConfig = surface.SurfaceEntity.DeviceEntities.FirstOrDefault(d => d.DeviceIdentifier == deviceIdentifier); if (existingDeviceConfig != null) { - Plugin plugin = _pluginService.GetPluginByDevice(rgbDevice); - device = new ArtemisDevice(rgbDevice, plugin, surface, existingDeviceConfig); + PluginFeature pluginFeature = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice); + device = new ArtemisDevice(rgbDevice, pluginFeature, surface, existingDeviceConfig); } // Fall back on creating a new device else @@ -189,8 +189,8 @@ namespace Artemis.Core.Services rgbDevice.DeviceInfo, deviceIdentifier ); - Plugin plugin = _pluginService.GetPluginByDevice(rgbDevice); - device = new ArtemisDevice(rgbDevice, plugin, surface); + PluginFeature pluginFeature = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice); + device = new ArtemisDevice(rgbDevice, pluginFeature, surface); } surface.Devices.Add(device); diff --git a/src/Artemis.Core/Stores/ConditionOperatorStore.cs b/src/Artemis.Core/Stores/ConditionOperatorStore.cs index 48125ac6a..3247d648d 100644 --- a/src/Artemis.Core/Stores/ConditionOperatorStore.cs +++ b/src/Artemis.Core/Stores/ConditionOperatorStore.cs @@ -16,7 +16,7 @@ namespace Artemis.Core if (Registrations.Any(r => r.ConditionOperator == conditionOperator)) throw new ArtemisCoreException($"Condition operator store store already contains operator '{conditionOperator.Description}'"); - registration = new ConditionOperatorRegistration(conditionOperator, conditionOperator.PluginInfo.Instance) {IsInStore = true}; + registration = new ConditionOperatorRegistration(conditionOperator, conditionOperator.Plugin) {IsInStore = true}; Registrations.Add(registration); } @@ -42,7 +42,7 @@ namespace Artemis.Core { lock (Registrations) { - return Registrations.FirstOrDefault(r => r.Plugin.PluginInfo.Guid == pluginGuid && r.ConditionOperator.GetType().Name == type); + return Registrations.FirstOrDefault(r => r.Plugin.Guid == pluginGuid && r.ConditionOperator.GetType().Name == type); } } diff --git a/src/Artemis.Core/Stores/DataBindingModifierTypeStore.cs b/src/Artemis.Core/Stores/DataBindingModifierTypeStore.cs index bd2402f3c..302f32241 100644 --- a/src/Artemis.Core/Stores/DataBindingModifierTypeStore.cs +++ b/src/Artemis.Core/Stores/DataBindingModifierTypeStore.cs @@ -16,7 +16,7 @@ namespace Artemis.Core if (Registrations.Any(r => r.DataBindingModifierType == modifierType)) throw new ArtemisCoreException($"Data binding modifier type store already contains modifier '{modifierType.Name}'"); - typeRegistration = new DataBindingModifierTypeRegistration(modifierType, modifierType.PluginInfo.Instance) { IsInStore = true }; + typeRegistration = new DataBindingModifierTypeRegistration(modifierType, modifierType.Plugin) { IsInStore = true }; Registrations.Add(typeRegistration); } @@ -42,7 +42,7 @@ namespace Artemis.Core { lock (Registrations) { - return Registrations.FirstOrDefault(r => r.Plugin.PluginInfo.Guid == pluginGuid && r.DataBindingModifierType.GetType().Name == type); + return Registrations.FirstOrDefault(r => r.Plugin.Guid == pluginGuid && r.DataBindingModifierType.GetType().Name == type); } } diff --git a/src/Artemis.Core/Stores/DataModelStore.cs b/src/Artemis.Core/Stores/DataModelStore.cs index 2617e91f7..713ee90c2 100644 --- a/src/Artemis.Core/Stores/DataModelStore.cs +++ b/src/Artemis.Core/Stores/DataModelStore.cs @@ -17,7 +17,7 @@ namespace Artemis.Core if (Registrations.Any(r => r.DataModel == dataModel)) throw new ArtemisCoreException($"Data model store already contains data model '{dataModel.DataModelDescription}'"); - registration = new DataModelRegistration(dataModel, dataModel.PluginInfo.Instance) {IsInStore = true}; + registration = new DataModelRegistration(dataModel, dataModel.Feature) {IsInStore = true}; Registrations.Add(registration); } @@ -47,11 +47,11 @@ namespace Artemis.Core } } - public static DataModelRegistration Get(Guid pluginGuid) + public static DataModelRegistration Get(string id) { lock (Registrations) { - return Registrations.FirstOrDefault(d => d.Plugin.PluginInfo.Guid == pluginGuid); + return Registrations.FirstOrDefault(d => d.PluginFeature.Id == id); } } diff --git a/src/Artemis.Core/Stores/LayerBrushStore.cs b/src/Artemis.Core/Stores/LayerBrushStore.cs index 9dd77dc9b..fb05da802 100644 --- a/src/Artemis.Core/Stores/LayerBrushStore.cs +++ b/src/Artemis.Core/Stores/LayerBrushStore.cs @@ -17,7 +17,7 @@ namespace Artemis.Core if (Registrations.Any(r => r.LayerBrushDescriptor == descriptor)) throw new ArtemisCoreException($"Store already contains layer brush '{descriptor.DisplayName}'"); - registration = new LayerBrushRegistration(descriptor, descriptor.LayerBrushProvider.PluginInfo.Instance) {IsInStore = true}; + registration = new LayerBrushRegistration(descriptor, descriptor.Provider) {IsInStore = true}; Registrations.Add(registration); } @@ -47,11 +47,11 @@ namespace Artemis.Core } } - public static LayerBrushRegistration Get(Guid pluginGuid, string typeName) + public static LayerBrushRegistration? Get(string id, string typeName) { lock (Registrations) { - return Registrations.FirstOrDefault(d => d.Plugin.PluginInfo.Guid == pluginGuid && + return Registrations.FirstOrDefault(d => d.PluginFeature.Id == id && d.LayerBrushDescriptor.LayerBrushType.Name == typeName); } } diff --git a/src/Artemis.Core/Stores/LayerEffectStore.cs b/src/Artemis.Core/Stores/LayerEffectStore.cs index 7e78e22bc..ac3060bd9 100644 --- a/src/Artemis.Core/Stores/LayerEffectStore.cs +++ b/src/Artemis.Core/Stores/LayerEffectStore.cs @@ -17,7 +17,7 @@ namespace Artemis.Core if (Registrations.Any(r => r.LayerEffectDescriptor == descriptor)) throw new ArtemisCoreException($"Store already contains layer brush '{descriptor.DisplayName}'"); - registration = new LayerEffectRegistration(descriptor, descriptor.LayerEffectProvider.PluginInfo.Instance) { IsInStore = true }; + registration = new LayerEffectRegistration(descriptor, descriptor.Provider) { IsInStore = true }; Registrations.Add(registration); } @@ -47,11 +47,11 @@ namespace Artemis.Core } } - public static LayerEffectRegistration Get(Guid pluginGuid, string typeName) + public static LayerEffectRegistration? Get(string providerId, string typeName) { lock (Registrations) { - return Registrations.FirstOrDefault(d => d.Plugin.PluginInfo.Guid == pluginGuid && d.LayerEffectDescriptor.LayerEffectType.Name == typeName); + return Registrations.FirstOrDefault(d => d.PluginFeature.Id == providerId && d.LayerEffectDescriptor.LayerEffectType.Name == typeName); } } diff --git a/src/Artemis.Core/Stores/Registrations/ConditionOperatorRegistration.cs b/src/Artemis.Core/Stores/Registrations/ConditionOperatorRegistration.cs index 1b0e6c109..46cb22068 100644 --- a/src/Artemis.Core/Stores/Registrations/ConditionOperatorRegistration.cs +++ b/src/Artemis.Core/Stores/Registrations/ConditionOperatorRegistration.cs @@ -12,7 +12,7 @@ namespace Artemis.Core ConditionOperator = conditionOperator; Plugin = plugin; - Plugin.PluginDisabled += PluginOnPluginDisabled; + Plugin.Disabled += OnDisabled; } /// @@ -30,9 +30,9 @@ namespace Artemis.Core /// public bool IsInStore { get; internal set; } - private void PluginOnPluginDisabled(object sender, EventArgs e) + private void OnDisabled(object sender, EventArgs e) { - Plugin.PluginDisabled -= PluginOnPluginDisabled; + Plugin.Disabled -= OnDisabled; if (IsInStore) ConditionOperatorStore.Remove(this); } diff --git a/src/Artemis.Core/Stores/Registrations/DataBindingModifierTypeRegistration.cs b/src/Artemis.Core/Stores/Registrations/DataBindingModifierTypeRegistration.cs index a49f32fa9..b0ff66088 100644 --- a/src/Artemis.Core/Stores/Registrations/DataBindingModifierTypeRegistration.cs +++ b/src/Artemis.Core/Stores/Registrations/DataBindingModifierTypeRegistration.cs @@ -12,7 +12,7 @@ namespace Artemis.Core DataBindingModifierType = dataBindingModifierType; Plugin = plugin; - Plugin.PluginDisabled += PluginOnPluginDisabled; + Plugin.Disabled += OnDisabled; } /// @@ -30,9 +30,9 @@ namespace Artemis.Core /// public bool IsInStore { get; internal set; } - private void PluginOnPluginDisabled(object sender, EventArgs e) + private void OnDisabled(object sender, EventArgs e) { - Plugin.PluginDisabled -= PluginOnPluginDisabled; + Plugin.Disabled -= OnDisabled; if (IsInStore) DataBindingModifierTypeStore.Remove(this); } diff --git a/src/Artemis.Core/Stores/Registrations/DataModelRegistration.cs b/src/Artemis.Core/Stores/Registrations/DataModelRegistration.cs index 336a64c96..7a1812e02 100644 --- a/src/Artemis.Core/Stores/Registrations/DataModelRegistration.cs +++ b/src/Artemis.Core/Stores/Registrations/DataModelRegistration.cs @@ -8,12 +8,12 @@ namespace Artemis.Core /// public class DataModelRegistration { - internal DataModelRegistration(DataModel dataModel, Plugin plugin) + internal DataModelRegistration(DataModel dataModel, PluginFeature pluginFeature) { DataModel = dataModel; - Plugin = plugin; + PluginFeature = pluginFeature; - Plugin.PluginDisabled += PluginOnPluginDisabled; + PluginFeature.Disabled += OnDisabled; } /// @@ -24,16 +24,16 @@ namespace Artemis.Core /// /// Gets the plugin the data model is associated with /// - public Plugin Plugin { get; } + public PluginFeature PluginFeature { get; } /// /// Gets a boolean indicating whether the registration is in the internal Core store /// public bool IsInStore { get; internal set; } - private void PluginOnPluginDisabled(object sender, EventArgs e) + private void OnDisabled(object sender, EventArgs e) { - Plugin.PluginDisabled -= PluginOnPluginDisabled; + PluginFeature.Disabled -= OnDisabled; if (IsInStore) DataModelStore.Remove(this); } diff --git a/src/Artemis.Core/Stores/Registrations/LayerBrushRegistration.cs b/src/Artemis.Core/Stores/Registrations/LayerBrushRegistration.cs index eeda91812..8e35bf905 100644 --- a/src/Artemis.Core/Stores/Registrations/LayerBrushRegistration.cs +++ b/src/Artemis.Core/Stores/Registrations/LayerBrushRegistration.cs @@ -8,12 +8,12 @@ namespace Artemis.Core /// public class LayerBrushRegistration { - internal LayerBrushRegistration(LayerBrushDescriptor descriptor, Plugin plugin) + internal LayerBrushRegistration(LayerBrushDescriptor descriptor, PluginFeature pluginFeature) { LayerBrushDescriptor = descriptor; - Plugin = plugin; + PluginFeature = pluginFeature; - Plugin.PluginDisabled += PluginOnPluginDisabled; + PluginFeature.Disabled += OnDisabled; } /// @@ -24,16 +24,16 @@ namespace Artemis.Core /// /// Gets the plugin the layer brush is associated with /// - public Plugin Plugin { get; } + public PluginFeature PluginFeature { get; } /// /// Gets a boolean indicating whether the registration is in the internal Core store /// public bool IsInStore { get; internal set; } - private void PluginOnPluginDisabled(object sender, EventArgs e) + private void OnDisabled(object sender, EventArgs e) { - Plugin.PluginDisabled -= PluginOnPluginDisabled; + PluginFeature.Disabled -= OnDisabled; if (IsInStore) LayerBrushStore.Remove(this); } diff --git a/src/Artemis.Core/Stores/Registrations/LayerEffectRegistration.cs b/src/Artemis.Core/Stores/Registrations/LayerEffectRegistration.cs index 992acf616..0b74524f0 100644 --- a/src/Artemis.Core/Stores/Registrations/LayerEffectRegistration.cs +++ b/src/Artemis.Core/Stores/Registrations/LayerEffectRegistration.cs @@ -8,12 +8,12 @@ namespace Artemis.Core /// public class LayerEffectRegistration { - internal LayerEffectRegistration(LayerEffectDescriptor descriptor, Plugin plugin) + internal LayerEffectRegistration(LayerEffectDescriptor descriptor, PluginFeature pluginFeature) { LayerEffectDescriptor = descriptor; - Plugin = plugin; + PluginFeature = pluginFeature; - Plugin.PluginDisabled += PluginOnPluginDisabled; + PluginFeature.Disabled += OnDisabled; } /// @@ -24,16 +24,16 @@ namespace Artemis.Core /// /// Gets the plugin the layer effect is associated with /// - public Plugin Plugin { get; } + public PluginFeature PluginFeature { get; } /// /// Gets a boolean indicating whether the registration is in the internal Core store /// public bool IsInStore { get; internal set; } - private void PluginOnPluginDisabled(object sender, EventArgs e) + private void OnDisabled(object sender, EventArgs e) { - Plugin.PluginDisabled -= PluginOnPluginDisabled; + PluginFeature.Disabled -= OnDisabled; if (IsInStore) LayerEffectStore.Remove(this); } diff --git a/src/Artemis.Core/Utilities/CorePlugin.cs b/src/Artemis.Core/Utilities/CorePlugin.cs deleted file mode 100644 index 8a87a9028..000000000 --- a/src/Artemis.Core/Utilities/CorePlugin.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Artemis.Core.LayerEffects; - -namespace Artemis.Core -{ - /// - /// An empty plugin used by - /// - internal class CorePlugin : Plugin - { - public CorePlugin() - { - Constants.CorePluginInfo.Instance = this; - Enabled = true; - } - - public override void EnablePlugin() - { - } - - public override void DisablePlugin() - { - } - } - - internal class EffectPlaceholderPlugin : LayerEffectProvider - { - public EffectPlaceholderPlugin() - { - Enabled = true; - } - - public override void EnablePlugin() - { - } - - public override void DisablePlugin() - { - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Utilities/CorePluginFeature.cs b/src/Artemis.Core/Utilities/CorePluginFeature.cs new file mode 100644 index 000000000..897c46b88 --- /dev/null +++ b/src/Artemis.Core/Utilities/CorePluginFeature.cs @@ -0,0 +1,40 @@ +using Artemis.Core.LayerEffects; + +namespace Artemis.Core +{ + /// + /// An empty data model plugin feature used by + /// + internal class CorePluginFeature : DataModelPluginFeature + { + public CorePluginFeature() + { + Constants.CorePlugin.AddFeature(this); + IsEnabled = true; + } + + public override void Enable() + { + } + + public override void Disable() + { + } + } + + internal class EffectPlaceholderPlugin : LayerEffectProvider + { + public EffectPlaceholderPlugin() + { + IsEnabled = true; + } + + public override void Enable() + { + } + + public override void Disable() + { + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Utilities/CurrentProcessUtilities.cs b/src/Artemis.Core/Utilities/CurrentProcessUtilities.cs index 464aa628f..4c3a03463 100644 --- a/src/Artemis.Core/Utilities/CurrentProcessUtilities.cs +++ b/src/Artemis.Core/Utilities/CurrentProcessUtilities.cs @@ -1,6 +1,6 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; using System.Windows; -using Stylet; namespace Artemis.Core { @@ -37,8 +37,8 @@ namespace Artemis.Core if (!Debugger.IsAttached) Process.Start(info); - // Also attempt a graceful shutdown on the UI thread - Execute.OnUIThread(() => Application.Current.Shutdown()); + // Request a graceful shutdown, whatever UI we're running can pick this up + OnShutdownRequested(); } /// @@ -64,5 +64,18 @@ namespace Artemis.Core { return Process.GetCurrentProcess().MainModule.FileName; } + + #region Events + + public static event EventHandler ShutdownRequested; + + private static void OnShutdownRequested() + { + ShutdownRequested?.Invoke(null, EventArgs.Empty); + } + + #endregion + + } } \ No newline at end of file diff --git a/src/Artemis.Core/Utilities/IntroAnimation.cs b/src/Artemis.Core/Utilities/IntroAnimation.cs index 0529bfb90..8030b10fb 100644 --- a/src/Artemis.Core/Utilities/IntroAnimation.cs +++ b/src/Artemis.Core/Utilities/IntroAnimation.cs @@ -67,12 +67,12 @@ namespace Artemis.Core internal class DummyModule : ProfileModule { - public override void EnablePlugin() + public override void Enable() { throw new NotImplementedException(); } - public override void DisablePlugin() + public override void Disable() { throw new NotImplementedException(); } diff --git a/src/Artemis.Core/packages.lock.json b/src/Artemis.Core/packages.lock.json index e6ecc5b44..253535ea9 100644 --- a/src/Artemis.Core/packages.lock.json +++ b/src/Artemis.Core/packages.lock.json @@ -121,15 +121,6 @@ "System.Memory": "4.5.3" } }, - "Stylet": { - "type": "Direct", - "requested": "[1.3.4, )", - "resolved": "1.3.4", - "contentHash": "bCEdA+AIi+TM9SQQGLYMsFRIfzZcDUDg2Mznyr72kOkcC/cdBj01/jel4/v2aoKwbFcxVjiqmpgnbsFgMEZ4zQ==", - "dependencies": { - "System.Drawing.Common": "4.6.0" - } - }, "System.Buffers": { "type": "Direct", "requested": "[4.5.0, )", @@ -192,8 +183,8 @@ }, "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "3.0.0", - "contentHash": "TsETIgVJb/AKoYfSP+iCxkuly5d3inZjTdx/ItZLk2CxY85v8083OBS3uai84kK3/baLnS5/b5XGs6zR7SuuHQ==" + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", @@ -210,14 +201,6 @@ "System.Runtime": "4.3.0" } }, - "Microsoft.Win32.SystemEvents": { - "type": "Transitive", - "resolved": "4.6.0", - "contentHash": "Edg+pFW5C8WJb680Za2kTV8TqUi6Ahl/WldRVoOVJ23UQLpDHFspa+umgFjkWZw24ETsU99Cg+ErZz683M4chg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "3.0.0" - } - }, "NETStandard.Library": { "type": "Transitive", "resolved": "1.6.1", @@ -557,15 +540,6 @@ "System.Runtime": "4.3.0" } }, - "System.Drawing.Common": { - "type": "Transitive", - "resolved": "4.6.0", - "contentHash": "2A3spjjoPZnvpVh/sDTzd+0H8ZqTdr+hH/6obB8MMfG81EJ85PmxCKDBxhBVQiA25PliKAZ1sKogDcq9mSnFEA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "3.0.0", - "Microsoft.Win32.SystemEvents": "4.6.0" - } - }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.3.0", diff --git a/src/Artemis.Storage/Entities/Module/ModuleSettingsEntity.cs b/src/Artemis.Storage/Entities/Module/ModuleSettingsEntity.cs index 296a5ca8a..3cddf93b7 100644 --- a/src/Artemis.Storage/Entities/Module/ModuleSettingsEntity.cs +++ b/src/Artemis.Storage/Entities/Module/ModuleSettingsEntity.cs @@ -10,7 +10,7 @@ namespace Artemis.Storage.Entities.Module } public Guid Id { get; set; } - public Guid PluginGuid { get; set; } + public string ModuleId { get; set; } public int PriorityCategory { get; set; } public int Priority { get; set; } } diff --git a/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs b/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs index 65b84733f..4ac4db668 100644 --- a/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs +++ b/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace Artemis.Storage.Entities.Plugins { @@ -7,7 +8,23 @@ namespace Artemis.Storage.Entities.Plugins /// public class PluginEntity { + public PluginEntity() + { + Features = new List(); + } + public Guid Id { get; set; } public bool IsEnabled { get; set; } + + public List Features { get; set; } + } + + /// + /// Represents the configuration of a plugin feature, each feature has one configuration + /// + public class PluginFeatureEntity + { + public string Type { get; set; } + public bool IsEnabled { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/DataModelPathEntity.cs b/src/Artemis.Storage/Entities/Profile/DataModelPathEntity.cs index b4a2b03b5..7ab2806db 100644 --- a/src/Artemis.Storage/Entities/Profile/DataModelPathEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/DataModelPathEntity.cs @@ -5,7 +5,7 @@ namespace Artemis.Storage.Entities.Profile public class DataModelPathEntity { public string Path { get; set; } - public Guid? DataModelGuid { get; set; } + public string DataModelId { get; set; } public PathWrapperType WrapperType { get; set; } } diff --git a/src/Artemis.Storage/Entities/Profile/LayerEffectEntity.cs b/src/Artemis.Storage/Entities/Profile/LayerEffectEntity.cs index d6dce29be..6c8503e42 100644 --- a/src/Artemis.Storage/Entities/Profile/LayerEffectEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/LayerEffectEntity.cs @@ -5,7 +5,7 @@ namespace Artemis.Storage.Entities.Profile public class LayerEffectEntity { public Guid Id { get; set; } - public Guid PluginGuid { get; set; } + public string ProviderId { get; set; } public string EffectType { get; set; } public string Name { get; set; } public bool Enabled { get; set; } diff --git a/src/Artemis.Storage/Entities/Profile/ProfileEntity.cs b/src/Artemis.Storage/Entities/Profile/ProfileEntity.cs index ecd08497b..177d8c00b 100644 --- a/src/Artemis.Storage/Entities/Profile/ProfileEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/ProfileEntity.cs @@ -13,7 +13,7 @@ namespace Artemis.Storage.Entities.Profile } public Guid Id { get; set; } - public Guid PluginGuid { get; set; } + public string ModuleId { get; set; } public string Name { get; set; } public bool IsActive { get; set; } diff --git a/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs b/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs index c26baa1ce..6c04025d4 100644 --- a/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Artemis.Storage.Entities.Profile.DataBindings; namespace Artemis.Storage.Entities.Profile @@ -12,7 +11,7 @@ namespace Artemis.Storage.Entities.Profile DataBindingEntities = new List(); } - public Guid PluginGuid { get; set; } + public string FeatureId { get; set; } public string Path { get; set; } public string Value { get; set; } diff --git a/src/Artemis.Storage/Migrations/M8PluginFeatures.cs b/src/Artemis.Storage/Migrations/M8PluginFeatures.cs new file mode 100644 index 000000000..aaad14c7b --- /dev/null +++ b/src/Artemis.Storage/Migrations/M8PluginFeatures.cs @@ -0,0 +1,137 @@ +using System.Collections.Generic; +using Artemis.Storage.Entities.Module; +using Artemis.Storage.Migrations.Interfaces; +using LiteDB; + +namespace Artemis.Storage.Migrations +{ + public class M8PluginFeatures : IStorageMigration + { + private void Migrate(BsonValue bsonValue, Dictionary pluginMap) + { + if (bsonValue.IsArray) + { + foreach (BsonValue child in bsonValue.AsArray) + Migrate(child, pluginMap); + return; + } + + if (bsonValue.IsDocument) + { + // Data model paths + ReplaceIfFound(bsonValue, "DataModelGuid", "DataModelId", pluginMap); + // Layer effects + if (bsonValue.AsDocument.ContainsKey("EffectType")) + ReplaceIfFound(bsonValue, "PluginGuid", "ProviderId", pluginMap); + // Properties + if (bsonValue.AsDocument.ContainsKey("KeyframesEnabled")) + ReplaceIfFound(bsonValue, "PluginGuid", "FeatureId", pluginMap); + + foreach (BsonValue documentValue in bsonValue.AsDocument.Values) + Migrate(documentValue, pluginMap); + } + } + + private bool ReplaceIfFound(BsonValue bsonValue, string oldKey, string newKey, Dictionary pluginMap) + { + if (bsonValue.AsDocument.TryGetValue(oldKey, out BsonValue dataModelValue)) + if (pluginMap.TryGetValue(dataModelValue.AsGuid.ToString(), out string featureId)) + { + bsonValue.AsDocument[newKey] = featureId; + bsonValue.AsDocument.Remove(oldKey); + + return true; + } + + return false; + } + + public int UserVersion => 8; + + public void Apply(LiteRepository repository) + { + Dictionary pluginMap = new Dictionary + { + {"ffffffff-ffff-ffff-ffff-ffffffffffff", "Artemis.Core.CorePluginFeature-ffffffff"}, + {"ab41d601-35e0-4a73-bf0b-94509b006ab0", "Artemis.Plugins.DataModelExpansions.TestData.PluginDataModelExpansion-ab41d601"}, + {"c20e876f-7cb0-4fa1-b0cc-ae1afb5865d1", "Artemis.Plugins.Devices.Asus.AsusDeviceProvider-c20e876f"}, + {"b78f644b-827f-4bb4-bf03-2adaa365b58b", "Artemis.Plugins.Devices.CoolerMaster.CoolerMasterDeviceProvider-b78f644b"}, + {"926629ab-8170-42f3-be18-22c694aa91cd", "Artemis.Plugins.Devices.Corsair.CorsairDeviceProvider-926629ab"}, + {"cad475d3-c621-4ec7-bbfc-784e3b4723ce", "Artemis.Plugins.Devices.Debug.DebugDeviceProvider-cad475d3"}, + {"6f073d4d-d97d-4040-9750-841fdbe06915", "Artemis.Plugins.Devices.DMX.DMXDeviceProvider-6f073d4d"}, + {"62a45c0c-884c-4868-9fd7-3c5987fe07ca", "Artemis.Plugins.Devices.Logitech.LogitechDeviceProvider-62a45c0c"}, + {"9177c320-1206-48a3-af52-b1749c758786", "Artemis.Plugins.Devices.Msi.MsiDeviceProvider-9177c320"}, + {"a487332f-c4b3-43e7-b80f-f33adc6fff87", "Artemis.Plugins.Devices.Novation.NovationDeviceProvider-a487332f"}, + {"58a3d80e-d5cb-4a40-9465-c0a5d54825d6", "Artemis.Plugins.Devices.Razer.RazerDeviceProvider-58a3d80e"}, + {"10049953-94c1-4102-988b-9e4f0b64c232", "Artemis.Plugins.Devices.Roccat.RoccatDeviceProvider-10049953"}, + {"27945704-6edd-48b4-bc0e-319cce9693fc", "Artemis.Plugins.Devices.SteelSeries.SteelSeriesDeviceProvider-27945704"}, + {"e70fd5ba-9881-480a-8ff6-078ed5f747fa", "Artemis.Plugins.Devices.Wooting.WootingDeviceProvider-e70fd5ba"}, + {"ec86de32-1010-4bf7-97d7-1dcc46659ab6", "Artemis.Plugins.Devices.WS281X.WS281XDeviceProvider-ec86de32"}, + {"92a9d6ba-6f7a-4937-94d5-c1d715b4141a", "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba"}, + {"0bbf931b-87ad-4809-9cd9-bda33f4d4695", "Artemis.Plugins.LayerBrushes.ColorRgbNet.RgbNetColorBrushProvider-0bbf931b"}, + {"61cbbf01-8d69-4ede-a972-f3f269da66d9", "Artemis.Plugins.LayerBrushes.Noise.NoiseBrushProvider-61cbbf01"}, + {"0cb99d89-915b-407e-82ac-8316d0559c4e", "Artemis.Plugins.LayerBrushes.Chroma.ChromaLayerBrushProvider-0cb99d89"}, + {"bf9cf3ac-9f97-4328-b32f-aa39df1698ff", "Artemis.Plugins.LayerBrushes.Gif.GifLayerBrushProvider-bf9cf3ac"}, + {"4570510a-7c8b-4324-b915-cea738a65ac2", "Artemis.Plugins.LayerBrushes.Particle.ParticleLayerBrushProvider-4570510a"}, + {"245aa860-4224-4d1c-ab81-2d6b5593a9fa", "Artemis.Plugins.LayerBrushes.Spectrum.PluginLayerBrushProvider-245aa860"}, + {"fca5b5d6-3f86-4ea7-a271-06ec3fc219e2", "Artemis.Plugins.LayerEffects.Filter.FilterEffectProvider-fca5b5d6"}, + {"0de2991a-d7b8-4f61-ae4e-6623849215b5", "Artemis.Plugins.Modules.General.GeneralModule-0de2991a"}, + {"29e3ff97-83a5-44fc-a2dc-04f446b54146", "Artemis.Plugins.Modules.Overlay.OverlayModule-29e3ff97"}, + {"ea2064cc-63ad-4a22-93e3-bfc71beb6c4b", "Artemis.Plugins.Modules.Fallout4.Fallout4Module-ea2064cc"}, + {"a34dd13c-2d66-47a3-91fc-265749144d01", "Artemis.Plugins.Modules.LeagueOfLegends.LeagueOfLegendsModule-a34dd13c"}, + {"49dff3a6-3d2a-444a-8346-582b2d61b776", "Module.EliteDangerous.EliteDangerousModule-49dff3a6"}, + {"8f493ff1-0590-4c70-8a1d-e599e5580d21", "Artemis.Plugins.Modules.TruckSimulator.TruckSimulatorModule-8f493ff1"}, + {"184ce933-b8ff-465f-b3d2-a23a17b35f65", "Artemis.Plugins.PhilipsHue.HueDataModelExpansion-184ce933"} + }; + + // Remove the default brush the user selected, this will make the UI pick a new one + repository.Database.Execute("DELETE PluginSettingEntity WHERE $.Name = \"ProfileEditor.DefaultLayerBrushDescriptor\""); + + // Module settings + repository.Database.GetCollection().DropIndex("PluginGuid"); + ILiteCollection modules = repository.Database.GetCollection("ModuleSettingsEntity"); + foreach (BsonDocument bsonDocument in modules.FindAll()) + { + if (ReplaceIfFound(bsonDocument, "PluginGuid", "ModuleId", pluginMap)) + modules.Update(bsonDocument); + else if (bsonDocument.ContainsKey("PluginGuid")) + modules.Delete(bsonDocument["_id"]); + } + repository.Database.GetCollection().EnsureIndex(s => s.ModuleId, true); + + // Profiles + ILiteCollection collection = repository.Database.GetCollection("ProfileEntity"); + foreach (BsonDocument bsonDocument in collection.FindAll()) + { + ReplaceIfFound(bsonDocument, "PluginGuid", "ModuleId", pluginMap); + + foreach (BsonValue bsonLayer in bsonDocument["Layers"].AsArray) + { + Migrate(bsonLayer, pluginMap); + MigrateProperties(bsonLayer, pluginMap); + } + + foreach (BsonValue bsonLayer in bsonDocument["Folders"].AsArray) + { + Migrate(bsonLayer, pluginMap); + MigrateProperties(bsonLayer, pluginMap); + } + + collection.Update(bsonDocument); + } + } + + private void MigrateProperties(BsonValue profileElementBson, Dictionary pluginMap) + { + foreach (BsonValue bsonValue in profileElementBson["PropertyEntities"].AsArray) + { + if (bsonValue["Path"].AsString == "General.BrushReference") + { + bsonValue["Value"] = bsonValue["Value"].AsString.Replace("BrushPluginGuid", "LayerBrushProviderId"); + foreach ((string key, string value) in pluginMap) + bsonValue["Value"] = bsonValue["Value"].AsString.Replace(key, value); + } + } + } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IModuleRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IModuleRepository.cs index 5780e7bc0..918efb361 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IModuleRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IModuleRepository.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Artemis.Storage.Entities.Module; namespace Artemis.Storage.Repositories.Interfaces @@ -7,7 +6,7 @@ namespace Artemis.Storage.Repositories.Interfaces public interface IModuleRepository : IRepository { void Add(ModuleSettingsEntity moduleSettingsEntity); - ModuleSettingsEntity GetByPluginGuid(Guid guid); + ModuleSettingsEntity GetByModuleId(string moduleId); List GetAll(); List GetByCategory(int category); void Save(ModuleSettingsEntity moduleSettingsEntity); diff --git a/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs index d0f559b16..a42c74966 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs @@ -10,7 +10,7 @@ namespace Artemis.Storage.Repositories.Interfaces void Remove(ProfileEntity profileEntity); List GetAll(); ProfileEntity Get(Guid id); - List GetByPluginGuid(Guid pluginGuid); + List GetByModuleId(string moduleId); void Save(ProfileEntity profileEntity); } } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/ModuleRepository.cs b/src/Artemis.Storage/Repositories/ModuleRepository.cs index 6f3ad34b7..9d733507c 100644 --- a/src/Artemis.Storage/Repositories/ModuleRepository.cs +++ b/src/Artemis.Storage/Repositories/ModuleRepository.cs @@ -6,14 +6,14 @@ using LiteDB; namespace Artemis.Storage.Repositories { - public class ModuleRepository : IModuleRepository + internal class ModuleRepository : IModuleRepository { private readonly LiteRepository _repository; - internal ModuleRepository(LiteRepository repository) + public ModuleRepository(LiteRepository repository) { _repository = repository; - _repository.Database.GetCollection().EnsureIndex(s => s.PluginGuid, true); + _repository.Database.GetCollection().EnsureIndex(s => s.ModuleId, true); } public void Add(ModuleSettingsEntity moduleSettingsEntity) @@ -21,9 +21,9 @@ namespace Artemis.Storage.Repositories _repository.Insert(moduleSettingsEntity); } - public ModuleSettingsEntity GetByPluginGuid(Guid guid) + public ModuleSettingsEntity GetByModuleId(string moduleId) { - return _repository.FirstOrDefault(s => s.PluginGuid == guid); + return _repository.FirstOrDefault(s => s.ModuleId == moduleId); } public List GetAll() diff --git a/src/Artemis.Storage/Repositories/PluginRepository.cs b/src/Artemis.Storage/Repositories/PluginRepository.cs index 7d0f79652..b97c8fc83 100644 --- a/src/Artemis.Storage/Repositories/PluginRepository.cs +++ b/src/Artemis.Storage/Repositories/PluginRepository.cs @@ -5,11 +5,11 @@ using LiteDB; namespace Artemis.Storage.Repositories { - public class PluginRepository : IPluginRepository + internal class PluginRepository : IPluginRepository { private readonly LiteRepository _repository; - internal PluginRepository(LiteRepository repository) + public PluginRepository(LiteRepository repository) { _repository = repository; diff --git a/src/Artemis.Storage/Repositories/ProfileRepository.cs b/src/Artemis.Storage/Repositories/ProfileRepository.cs index 5ca90ea2e..1fdc88868 100644 --- a/src/Artemis.Storage/Repositories/ProfileRepository.cs +++ b/src/Artemis.Storage/Repositories/ProfileRepository.cs @@ -6,11 +6,11 @@ using LiteDB; namespace Artemis.Storage.Repositories { - public class ProfileRepository : IProfileRepository + internal class ProfileRepository : IProfileRepository { private readonly LiteRepository _repository; - internal ProfileRepository(LiteRepository repository) + public ProfileRepository(LiteRepository repository) { _repository = repository; _repository.Database.GetCollection().EnsureIndex(s => s.Name); @@ -36,12 +36,12 @@ namespace Artemis.Storage.Repositories return _repository.FirstOrDefault(p => p.Id == id); } - public List GetByPluginGuid(Guid pluginGuid) + public List GetByModuleId(string moduleId) { return _repository.Query() .Include(p => p.Folders) .Include(p => p.Layers) - .Where(s => s.PluginGuid == pluginGuid) + .Where(s => s.ModuleId == moduleId) .ToList(); } diff --git a/src/Artemis.Storage/Repositories/SurfaceRepository.cs b/src/Artemis.Storage/Repositories/SurfaceRepository.cs index 8d58b7d39..85f5c6cbd 100644 --- a/src/Artemis.Storage/Repositories/SurfaceRepository.cs +++ b/src/Artemis.Storage/Repositories/SurfaceRepository.cs @@ -5,11 +5,11 @@ using LiteDB; namespace Artemis.Storage.Repositories { - public class SurfaceRepository : ISurfaceRepository + internal class SurfaceRepository : ISurfaceRepository { private readonly LiteRepository _repository; - internal SurfaceRepository(LiteRepository repository) + public SurfaceRepository(LiteRepository repository) { _repository = repository; _repository.Database.GetCollection().EnsureIndex(s => s.Name); diff --git a/src/Artemis.Storage/StorageMigrationService.cs b/src/Artemis.Storage/StorageMigrationService.cs index dc71f6542..d5325a5b6 100644 --- a/src/Artemis.Storage/StorageMigrationService.cs +++ b/src/Artemis.Storage/StorageMigrationService.cs @@ -30,7 +30,11 @@ namespace Artemis.Storage _logger.Information("Applying storage migration {storageMigration} to update DB from v{oldVersion} to v{newVersion}", storageMigration.GetType().Name, _repository.Database.UserVersion, storageMigration.UserVersion); + + _repository.Database.BeginTrans(); storageMigration.Apply(_repository); + _repository.Database.Commit(); + _repository.Database.UserVersion = storageMigration.UserVersion; } } diff --git a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj.DotSettings b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj.DotSettings index 1292ea906..5229191ac 100644 --- a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj.DotSettings +++ b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj.DotSettings @@ -8,6 +8,7 @@ True True True + True True True True diff --git a/src/Artemis.UI.Shared/Behaviors/PutCursorAtEndTextBoxBehavior.cs b/src/Artemis.UI.Shared/Behaviors/PutCursorAtEndTextBoxBehavior.cs index 011330894..9af87af13 100644 --- a/src/Artemis.UI.Shared/Behaviors/PutCursorAtEndTextBoxBehavior.cs +++ b/src/Artemis.UI.Shared/Behaviors/PutCursorAtEndTextBoxBehavior.cs @@ -4,7 +4,7 @@ using Microsoft.Xaml.Behaviors; namespace Artemis.UI.Shared { - public class PutCursorAtEndTextBoxBehavior : Behavior + public class PutCursorAtEndTextBox : Behavior { private TextBox _textBox; diff --git a/src/Artemis.UI.Shared/Behaviors/ScrollParentWhenAtMax.cs b/src/Artemis.UI.Shared/Behaviors/ScrollParentWhenAtMax.cs new file mode 100644 index 000000000..2cde2a73c --- /dev/null +++ b/src/Artemis.UI.Shared/Behaviors/ScrollParentWhenAtMax.cs @@ -0,0 +1,53 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using Microsoft.Xaml.Behaviors; + +namespace Artemis.UI.Shared +{ + public class ScrollParentWhenAtMax : Behavior + { + protected override void OnAttached() + { + base.OnAttached(); + AssociatedObject.PreviewMouseWheel += PreviewMouseWheel; + } + + protected override void OnDetaching() + { + AssociatedObject.PreviewMouseWheel -= PreviewMouseWheel; + base.OnDetaching(); + } + + private void PreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + ScrollViewer scrollViewer = GetVisualChild(AssociatedObject); + double scrollPos = scrollViewer.ContentVerticalOffset; + if (scrollPos == scrollViewer.ScrollableHeight && e.Delta < 0 + || scrollPos == 0 && e.Delta > 0) + { + e.Handled = true; + MouseWheelEventArgs e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta); + e2.RoutedEvent = UIElement.MouseWheelEvent; + AssociatedObject.RaiseEvent(e2); + } + } + + private static T GetVisualChild(DependencyObject parent) where T : Visual + { + T child = default; + + int numVisuals = VisualTreeHelper.GetChildrenCount(parent); + for (int i = 0; i < numVisuals; i++) + { + Visual v = (Visual) VisualTreeHelper.GetChild(parent, i); + child = v as T; + if (child == null) child = GetVisualChild(v); + if (child != null) break; + } + + return child; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs b/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs index ab795404e..22d9b951c 100644 --- a/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs +++ b/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs @@ -1,11 +1,11 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Windows.Data; using System.Windows.Media; using Artemis.Core; using SkiaSharp; -using Stylet; namespace Artemis.UI.Shared { @@ -14,13 +14,13 @@ namespace Artemis.UI.Shared /// Converts into a /// . /// - [ValueConversion(typeof(BindableCollection), typeof(GradientStopCollection))] + [ValueConversion(typeof(List), typeof(GradientStopCollection))] public class ColorGradientToGradientStopsConverter : IValueConverter { /// public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - BindableCollection colorGradients = (BindableCollection) value; + List colorGradients = (List) value; GradientStopCollection collection = new GradientStopCollection(); if (colorGradients == null) return collection; @@ -34,7 +34,7 @@ namespace Artemis.UI.Shared public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { GradientStopCollection collection = (GradientStopCollection) value; - BindableCollection colorGradients = new BindableCollection(); + List colorGradients = new List(); if (collection == null) return colorGradients; diff --git a/src/Artemis.UI.Shared/DataModelVisualization/DataModelVisualizationRegistration.cs b/src/Artemis.UI.Shared/DataModelVisualization/DataModelVisualizationRegistration.cs index ef29c8cf7..5ed47be76 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/DataModelVisualizationRegistration.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/DataModelVisualizationRegistration.cs @@ -11,22 +11,22 @@ namespace Artemis.UI.Shared public DataModelVisualizationRegistration(IDataModelUIService dataModelUIService, RegistrationType registrationType, - PluginInfo pluginInfo, + Plugin plugin, Type supportedType, Type viewModelType) { _dataModelUIService = dataModelUIService; RegistrationType = registrationType; - PluginInfo = pluginInfo; + Plugin = plugin; SupportedType = supportedType; ViewModelType = viewModelType; - if (PluginInfo != Constants.CorePluginInfo) - PluginInfo.Instance.PluginDisabled += InstanceOnPluginDisabled; + if (Plugin != Constants.CorePlugin) + Plugin.Disabled += InstanceOnDisabled; } public RegistrationType RegistrationType { get; } - public PluginInfo PluginInfo { get; } + public Plugin Plugin { get; } public Type SupportedType { get; } public Type ViewModelType { get; } @@ -34,11 +34,11 @@ namespace Artemis.UI.Shared internal void Unsubscribe() { - if (PluginInfo != Constants.CorePluginInfo) - PluginInfo.Instance.PluginDisabled -= InstanceOnPluginDisabled; + if (Plugin != Constants.CorePlugin) + Plugin.Disabled -= InstanceOnDisabled; } - private void InstanceOnPluginDisabled(object sender, EventArgs e) + private void InstanceOnDisabled(object sender, EventArgs e) { if (RegistrationType == RegistrationType.Input) _dataModelUIService.RemoveDataModelInput(this); diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs index 88f1a0933..dd5091d0c 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs @@ -29,7 +29,7 @@ namespace Artemis.UI.Shared.Input private bool _isEnabled = true; private string _placeholder = "Select a property"; - internal DataModelDynamicViewModel(Module module, ISettingsService settingsService, IDataModelUIService dataModelUIService) + public DataModelDynamicViewModel(Module module, ISettingsService settingsService, IDataModelUIService dataModelUIService) { _module = module; _dataModelUIService = dataModelUIService; diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticViewModel.cs index 11c993fc9..490b126c9 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticViewModel.cs @@ -25,7 +25,7 @@ namespace Artemis.UI.Shared.Input private object _value; private bool _displaySwitchButton; - internal DataModelStaticViewModel(Type targetType, DataModelPropertyAttribute targetDescription, IDataModelUIService dataModelUIService) + public DataModelStaticViewModel(Type targetType, DataModelPropertyAttribute targetDescription, IDataModelUIService dataModelUIService) { _dataModelUIService = dataModelUIService; _rootView = Application.Current.Windows.OfType().SingleOrDefault(x => x.IsActive); diff --git a/src/Artemis.UI.Shared/Ninject/Factories/ISharedVMFactory.cs b/src/Artemis.UI.Shared/Ninject/Factories/ISharedVMFactory.cs new file mode 100644 index 000000000..d15414099 --- /dev/null +++ b/src/Artemis.UI.Shared/Ninject/Factories/ISharedVMFactory.cs @@ -0,0 +1,17 @@ +using System; +using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; +using Artemis.UI.Shared.Input; + +namespace Artemis.UI.Shared.Ninject.Factories +{ + public interface ISharedVmFactory + { + } + + public interface IDataModelVmFactory : ISharedVmFactory + { + DataModelDynamicViewModel DataModelDynamicViewModel(Module module); + DataModelStaticViewModel DataModelStaticViewModel(Type targetType, DataModelPropertyAttribute targetDescription); + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Ninject/SharedUIModule.cs b/src/Artemis.UI.Shared/Ninject/SharedUIModule.cs index 15949fe33..7482e6347 100644 --- a/src/Artemis.UI.Shared/Ninject/SharedUIModule.cs +++ b/src/Artemis.UI.Shared/Ninject/SharedUIModule.cs @@ -1,4 +1,5 @@ using System; +using Artemis.UI.Shared.Ninject.Factories; using Artemis.UI.Shared.Services; using MaterialDesignThemes.Wpf; using Ninject.Extensions.Conventions; @@ -29,6 +30,15 @@ namespace Artemis.UI.Shared.Ninject }); Kernel.Bind().ToConstant(new SnackbarMessageQueue(TimeSpan.FromSeconds(5))).InSingletonScope(); + + // Bind UI factories + Kernel.Bind(x => + { + x.FromThisAssembly() + .SelectAllInterfaces() + .InheritedFrom() + .BindToFactory(); + }); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrushes/BrushConfigurationViewModel.cs b/src/Artemis.UI.Shared/Plugins/LayerBrushes/BrushConfigurationViewModel.cs similarity index 92% rename from src/Artemis.Core/Plugins/LayerBrushes/BrushConfigurationViewModel.cs rename to src/Artemis.UI.Shared/Plugins/LayerBrushes/BrushConfigurationViewModel.cs index 34a928f94..0c61885b0 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/BrushConfigurationViewModel.cs +++ b/src/Artemis.UI.Shared/Plugins/LayerBrushes/BrushConfigurationViewModel.cs @@ -1,6 +1,7 @@ -using Stylet; +using Artemis.Core.LayerBrushes; +using Stylet; -namespace Artemis.Core.LayerBrushes +namespace Artemis.UI.Shared.LayerBrushes { /// /// Represents a view model for a brush configuration window diff --git a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushConfigurationDialog.cs b/src/Artemis.UI.Shared/Plugins/LayerBrushes/LayerBrushConfigurationDialog.cs similarity index 80% rename from src/Artemis.Core/Plugins/LayerBrushes/LayerBrushConfigurationDialog.cs rename to src/Artemis.UI.Shared/Plugins/LayerBrushes/LayerBrushConfigurationDialog.cs index 5074e8d02..ed20b670b 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushConfigurationDialog.cs +++ b/src/Artemis.UI.Shared/Plugins/LayerBrushes/LayerBrushConfigurationDialog.cs @@ -1,6 +1,7 @@ using System; +using Artemis.Core.LayerBrushes; -namespace Artemis.Core.LayerBrushes +namespace Artemis.UI.Shared.LayerBrushes { /// public class LayerBrushConfigurationDialog : LayerBrushConfigurationDialog where T : BrushConfigurationViewModel @@ -12,7 +13,7 @@ namespace Artemis.Core.LayerBrushes /// /// Describes a UI tab for a layer brush /// - public abstract class LayerBrushConfigurationDialog + public abstract class LayerBrushConfigurationDialog : ILayerBrushConfigurationDialog { /// /// The layer brush this dialog belongs to diff --git a/src/Artemis.Core/Plugins/LayerEffects/EffectConfigurationViewModel.cs b/src/Artemis.UI.Shared/Plugins/LayerEffects/EffectConfigurationViewModel.cs similarity index 92% rename from src/Artemis.Core/Plugins/LayerEffects/EffectConfigurationViewModel.cs rename to src/Artemis.UI.Shared/Plugins/LayerEffects/EffectConfigurationViewModel.cs index effc967a3..aac795039 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/EffectConfigurationViewModel.cs +++ b/src/Artemis.UI.Shared/Plugins/LayerEffects/EffectConfigurationViewModel.cs @@ -1,6 +1,7 @@ -using Stylet; +using Artemis.Core.LayerEffects; +using Stylet; -namespace Artemis.Core.LayerEffects +namespace Artemis.UI.Shared.LayerEffects { /// /// Represents a view model for an effect configuration window diff --git a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectConfigurationDialog.cs b/src/Artemis.UI.Shared/Plugins/LayerEffects/LayerEffectConfigurationDialog.cs similarity index 80% rename from src/Artemis.Core/Plugins/LayerEffects/LayerEffectConfigurationDialog.cs rename to src/Artemis.UI.Shared/Plugins/LayerEffects/LayerEffectConfigurationDialog.cs index 674d3897e..604214b8c 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectConfigurationDialog.cs +++ b/src/Artemis.UI.Shared/Plugins/LayerEffects/LayerEffectConfigurationDialog.cs @@ -1,6 +1,7 @@ using System; +using Artemis.Core.LayerEffects; -namespace Artemis.Core.LayerEffects +namespace Artemis.UI.Shared.LayerEffects { /// public class LayerEffectConfigurationDialog : LayerEffectConfigurationDialog where T : EffectConfigurationViewModel @@ -12,7 +13,7 @@ namespace Artemis.Core.LayerEffects /// /// Describes a UI tab for a specific layer effect /// - public abstract class LayerEffectConfigurationDialog + public abstract class LayerEffectConfigurationDialog : ILayerEffectConfigurationDialog { /// /// The layer effect this dialog belongs to diff --git a/src/Artemis.Core/Plugins/Modules/ModuleViewModel.cs b/src/Artemis.UI.Shared/Plugins/Modules/ModuleViewModel.cs similarity index 83% rename from src/Artemis.Core/Plugins/Modules/ModuleViewModel.cs rename to src/Artemis.UI.Shared/Plugins/Modules/ModuleViewModel.cs index 7aeed4311..05222aed1 100644 --- a/src/Artemis.Core/Plugins/Modules/ModuleViewModel.cs +++ b/src/Artemis.UI.Shared/Plugins/Modules/ModuleViewModel.cs @@ -1,11 +1,12 @@ -using Stylet; +using Artemis.Core.Modules; +using Stylet; -namespace Artemis.Core.Modules +namespace Artemis.UI.Shared.Modules { /// /// The base class for any view model that belongs to a module /// - public abstract class ModuleViewModel : Screen + public abstract class ModuleViewModel : Screen, IModuleViewModel { /// /// The base class for any view model that belongs to a module diff --git a/src/Artemis.Core/Plugins/PluginConfigurationDialog.cs b/src/Artemis.UI.Shared/Plugins/PluginConfigurationDialog.cs similarity index 76% rename from src/Artemis.Core/Plugins/PluginConfigurationDialog.cs rename to src/Artemis.UI.Shared/Plugins/PluginConfigurationDialog.cs index d215a6ec3..7c7e62886 100644 --- a/src/Artemis.Core/Plugins/PluginConfigurationDialog.cs +++ b/src/Artemis.UI.Shared/Plugins/PluginConfigurationDialog.cs @@ -1,6 +1,7 @@ using System; +using Artemis.Core; -namespace Artemis.Core +namespace Artemis.UI.Shared { /// public class PluginConfigurationDialog : PluginConfigurationDialog where T : PluginConfigurationViewModel @@ -12,12 +13,12 @@ namespace Artemis.Core /// /// Describes a configuration dialog for a specific plugin /// - public abstract class PluginConfigurationDialog + public abstract class PluginConfigurationDialog : IPluginConfigurationDialog { /// /// The layer brush this dialog belongs to /// - internal Plugin Plugin { get; set; } + internal PluginFeature PluginFeature { get; set; } /// /// The type of view model the tab contains diff --git a/src/Artemis.Core/Plugins/PluginConfigurationViewModel.cs b/src/Artemis.UI.Shared/Plugins/PluginConfigurationViewModel.cs similarity index 86% rename from src/Artemis.Core/Plugins/PluginConfigurationViewModel.cs rename to src/Artemis.UI.Shared/Plugins/PluginConfigurationViewModel.cs index ea4169732..61e88a503 100644 --- a/src/Artemis.Core/Plugins/PluginConfigurationViewModel.cs +++ b/src/Artemis.UI.Shared/Plugins/PluginConfigurationViewModel.cs @@ -1,11 +1,12 @@ -using Stylet; +using Artemis.Core; +using Stylet; -namespace Artemis.Core +namespace Artemis.UI.Shared { /// /// Represents a view model for a plugin configuration window /// - public abstract class PluginConfigurationViewModel : Screen + public abstract class PluginConfigurationViewModel : Screen, IPluginConfigurationViewModel { /// /// Creates a new instance of the class diff --git a/src/Artemis.UI.Shared/PropertyInput/PropertyInputRegistration.cs b/src/Artemis.UI.Shared/PropertyInput/PropertyInputRegistration.cs index 0c1a1e601..3db1233fc 100644 --- a/src/Artemis.UI.Shared/PropertyInput/PropertyInputRegistration.cs +++ b/src/Artemis.UI.Shared/PropertyInput/PropertyInputRegistration.cs @@ -8,28 +8,28 @@ namespace Artemis.UI.Shared { private readonly IProfileEditorService _profileEditorService; - internal PropertyInputRegistration(IProfileEditorService profileEditorService, PluginInfo pluginInfo, Type supportedType, Type viewModelType) + internal PropertyInputRegistration(IProfileEditorService profileEditorService, Plugin plugin, Type supportedType, Type viewModelType) { _profileEditorService = profileEditorService; - PluginInfo = pluginInfo; + Plugin = plugin; SupportedType = supportedType; ViewModelType = viewModelType; - if (PluginInfo != Constants.CorePluginInfo) - PluginInfo.Instance.PluginDisabled += InstanceOnPluginDisabled; + if (Plugin != Constants.CorePlugin) + Plugin.Disabled += InstanceOnDisabled; } - public PluginInfo PluginInfo { get; } + public Plugin Plugin { get; } public Type SupportedType { get; } public Type ViewModelType { get; } internal void Unsubscribe() { - if (PluginInfo != Constants.CorePluginInfo) - PluginInfo.Instance.PluginDisabled -= InstanceOnPluginDisabled; + if (Plugin != Constants.CorePlugin) + Plugin.Disabled -= InstanceOnDisabled; } - private void InstanceOnPluginDisabled(object sender, EventArgs e) + private void InstanceOnDisabled(object sender, EventArgs e) { // Profile editor service will call Unsubscribe _profileEditorService.RemovePropertyInput(this); diff --git a/src/Artemis.UI.Shared/Services/DataModelUIService.cs b/src/Artemis.UI.Shared/Services/DataModelUIService.cs index 9f164e23d..bfcdb4da4 100644 --- a/src/Artemis.UI.Shared/Services/DataModelUIService.cs +++ b/src/Artemis.UI.Shared/Services/DataModelUIService.cs @@ -7,6 +7,7 @@ using Artemis.Core.Modules; using Artemis.Core.Services; using Artemis.UI.Shared.DefaultTypes.DataModel.Display; using Artemis.UI.Shared.Input; +using Artemis.UI.Shared.Ninject.Factories; using Ninject; using Ninject.Parameters; @@ -15,13 +16,15 @@ namespace Artemis.UI.Shared.Services internal class DataModelUIService : IDataModelUIService { private readonly IDataModelService _dataModelService; + private readonly IDataModelVmFactory _dataModelVmFactory; private readonly IKernel _kernel; private readonly List _registeredDataModelDisplays; private readonly List _registeredDataModelEditors; - public DataModelUIService(IDataModelService dataModelService, IKernel kernel) + public DataModelUIService(IDataModelService dataModelService, IDataModelVmFactory dataModelVmFactory, IKernel kernel) { _dataModelService = dataModelService; + _dataModelVmFactory = dataModelVmFactory; _kernel = kernel; _registeredDataModelEditors = new List(); _registeredDataModelDisplays = new List(); @@ -33,7 +36,7 @@ namespace Artemis.UI.Shared.Services public DataModelPropertiesViewModel GetMainDataModelVisualization() { DataModelPropertiesViewModel viewModel = new DataModelPropertiesViewModel(null, null, null); - foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels()) + foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels().OrderBy(d => d.DataModelDescription.Name)) viewModel.Children.Add(new DataModelPropertiesViewModel(dataModelExpansion, viewModel, new DataModelPath(dataModelExpansion))); // Update to populate children @@ -42,29 +45,48 @@ namespace Artemis.UI.Shared.Services return viewModel; } - public DataModelPropertiesViewModel GetPluginDataModelVisualization(Plugin plugin, bool includeMainDataModel) + public void UpdateModules(DataModelPropertiesViewModel mainDataModelVisualization) + { + List disabledChildren = mainDataModelVisualization.Children.Where(d => !d.DataModel.Feature.IsEnabled).ToList(); + foreach (DataModelVisualizationViewModel child in disabledChildren) + mainDataModelVisualization.Children.Remove(child); + + foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels().OrderBy(d => d.DataModelDescription.Name)) + { + if (mainDataModelVisualization.Children.All(c => c.DataModel != dataModelExpansion)) + { + mainDataModelVisualization.Children.Add( + new DataModelPropertiesViewModel(dataModelExpansion, mainDataModelVisualization, new DataModelPath(dataModelExpansion)) + ); + } + } + + mainDataModelVisualization.Update(this, null); + } + + public DataModelPropertiesViewModel GetPluginDataModelVisualization(PluginFeature pluginFeature, bool includeMainDataModel) { if (includeMainDataModel) { DataModelPropertiesViewModel mainDataModel = GetMainDataModelVisualization(); // If the main data model already includes the plugin data model we're done - if (mainDataModel.Children.Any(c => c.DataModel.PluginInfo.Instance == plugin)) + if (mainDataModel.Children.Any(c => c.DataModel.Feature == pluginFeature)) return mainDataModel; // Otherwise get just the plugin data model and add it - DataModelPropertiesViewModel pluginDataModel = GetPluginDataModelVisualization(plugin, false); + DataModelPropertiesViewModel pluginDataModel = GetPluginDataModelVisualization(pluginFeature, false); if (pluginDataModel != null) mainDataModel.Children.Add(pluginDataModel); return mainDataModel; } - DataModel dataModel = _dataModelService.GetPluginDataModel(plugin); + DataModel dataModel = _dataModelService.GetPluginDataModel(pluginFeature); if (dataModel == null) return null; DataModelPropertiesViewModel viewModel = new DataModelPropertiesViewModel(null, null, null); - viewModel.Children.Add(new DataModelPropertiesViewModel(dataModel, viewModel, null)); + viewModel.Children.Add(new DataModelPropertiesViewModel(dataModel, viewModel, new DataModelPath(dataModel))); // Update to populate children viewModel.Update(this, null); @@ -72,7 +94,7 @@ namespace Artemis.UI.Shared.Services return viewModel; } - public DataModelVisualizationRegistration RegisterDataModelInput(PluginInfo pluginInfo, IReadOnlyCollection compatibleConversionTypes = null) where T : DataModelInputViewModel + public DataModelVisualizationRegistration RegisterDataModelInput(Plugin plugin, IReadOnlyCollection compatibleConversionTypes = null) where T : DataModelInputViewModel { if (compatibleConversionTypes == null) compatibleConversionTypes = new List(); @@ -83,19 +105,16 @@ namespace Artemis.UI.Shared.Services DataModelVisualizationRegistration existing = _registeredDataModelEditors.FirstOrDefault(r => r.SupportedType == supportedType); if (existing != null) { - if (existing.PluginInfo != pluginInfo) - { - throw new ArtemisPluginException($"Cannot register data model input for type {supportedType.Name} " + - $"because an editor was already registered by {pluginInfo.Name}"); - } - + if (existing.Plugin != plugin) + throw new ArtemisPluginException($"Cannot register data model input for type {supportedType.Name} because an editor was already" + + $" registered by {existing.Plugin}"); return existing; } _kernel.Bind(viewModelType).ToSelf(); // Create the registration - DataModelVisualizationRegistration registration = new DataModelVisualizationRegistration(this, RegistrationType.Input, pluginInfo, supportedType, viewModelType) + DataModelVisualizationRegistration registration = new DataModelVisualizationRegistration(this, RegistrationType.Input, plugin, supportedType, viewModelType) { // Apply the compatible conversion types to the registration CompatibleConversionTypes = compatibleConversionTypes @@ -106,7 +125,7 @@ namespace Artemis.UI.Shared.Services } } - public DataModelVisualizationRegistration RegisterDataModelDisplay(PluginInfo pluginInfo) where T : DataModelDisplayViewModel + public DataModelVisualizationRegistration RegisterDataModelDisplay(Plugin plugin) where T : DataModelDisplayViewModel { Type viewModelType = typeof(T); lock (_registeredDataModelDisplays) @@ -115,17 +134,14 @@ namespace Artemis.UI.Shared.Services DataModelVisualizationRegistration existing = _registeredDataModelDisplays.FirstOrDefault(r => r.SupportedType == supportedType); if (existing != null) { - if (existing.PluginInfo != pluginInfo) - { - throw new ArtemisPluginException($"Cannot register data model display for type {supportedType.Name} " + - $"because an editor was already registered by {pluginInfo.Name}"); - } - + if (existing.Plugin != plugin) + throw new ArtemisPluginException($"Cannot register data model display for type {supportedType.Name} because an editor was already" + + $" registered by {existing.Plugin}"); return existing; } _kernel.Bind(viewModelType).ToSelf(); - DataModelVisualizationRegistration registration = new DataModelVisualizationRegistration(this, RegistrationType.Display, pluginInfo, supportedType, viewModelType); + DataModelVisualizationRegistration registration = new DataModelVisualizationRegistration(this, RegistrationType.Display, plugin, supportedType, viewModelType); _registeredDataModelDisplays.Add(registration); return registration; } @@ -167,7 +183,7 @@ namespace Artemis.UI.Shared.Services DataModelVisualizationRegistration match = _registeredDataModelDisplays.FirstOrDefault(d => d.SupportedType == propertyType); if (match != null) - result = (DataModelDisplayViewModel) match.PluginInfo.Kernel.Get(match.ViewModelType); + result = (DataModelDisplayViewModel) match.Plugin.Kernel.Get(match.ViewModelType); else if (!fallBackToDefault) result = null; else @@ -206,12 +222,12 @@ namespace Artemis.UI.Shared.Services public DataModelDynamicViewModel GetDynamicSelectionViewModel(Module module) { - return _kernel.Get(new ConstructorArgument("module", module)); + return _dataModelVmFactory.DataModelDynamicViewModel(module); } public DataModelStaticViewModel GetStaticInputViewModel(Type targetType, DataModelPropertyAttribute targetDescription) { - return _kernel.Get(new ConstructorArgument("targetType", targetType), new ConstructorArgument("targetDescription", targetDescription)); + return _dataModelVmFactory.DataModelStaticViewModel(targetType, targetDescription); } private DataModelInputViewModel InstantiateDataModelInputViewModel(DataModelVisualizationRegistration registration, DataModelPropertyAttribute description, object initialValue) @@ -220,11 +236,12 @@ namespace Artemis.UI.Shared.Services if (initialValue != null && initialValue.GetType() != registration.SupportedType) initialValue = Convert.ChangeType(initialValue, registration.SupportedType); - IParameter[] parameters = { + IParameter[] parameters = + { new ConstructorArgument("targetDescription", description), new ConstructorArgument("initialValue", initialValue) }; - DataModelInputViewModel viewModel = (DataModelInputViewModel) registration.PluginInfo.Kernel.Get(registration.ViewModelType, parameters); + DataModelInputViewModel viewModel = (DataModelInputViewModel) registration.Plugin.Kernel.Get(registration.ViewModelType, parameters); viewModel.CompatibleConversionTypes = registration.CompatibleConversionTypes; return viewModel; } diff --git a/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs b/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs index 08ab28760..a62dcf014 100644 --- a/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs +++ b/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs @@ -22,14 +22,14 @@ namespace Artemis.UI.Shared.Services private readonly IKernel _kernel; private readonly IViewManager _viewManager; private readonly IWindowManager _windowManager; - private readonly IPluginService _pluginService; + private readonly IPluginManagementService _pluginManagementService; - public DialogService(IKernel kernel, IViewManager viewManager, IWindowManager windowManager, IPluginService pluginService) + public DialogService(IKernel kernel, IViewManager viewManager, IWindowManager windowManager, IPluginManagementService pluginManagementService) { _kernel = kernel; _viewManager = viewManager; _windowManager = windowManager; - _pluginService = pluginService; + _pluginManagementService = pluginManagementService; } private async Task ShowDialog(IParameter[] parameters) where T : DialogViewModelBase @@ -109,11 +109,12 @@ namespace Artemis.UI.Shared.Services private async Task ShowDialogAt(string identifier, IParameter[] parameters) where T : DialogViewModelBase { - Plugin callingPlugin = _pluginService.GetCallingPlugin(); - if (parameters == null) throw new ArgumentNullException(nameof(parameters)); + Plugin callingPlugin = _pluginManagementService.GetCallingPlugin(); + if (parameters == null) + throw new ArgumentNullException(nameof(parameters)); if (callingPlugin != null) - return await ShowDialog(identifier, callingPlugin.PluginInfo.Kernel.Get(parameters)); + return await ShowDialog(identifier, callingPlugin.Kernel.Get(parameters)); return await ShowDialog(identifier, _kernel.Get(parameters)); } diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs index 7ad914a33..de53e6b7c 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs @@ -7,22 +7,114 @@ using Artemis.UI.Shared.Input; namespace Artemis.UI.Shared.Services { + /// + /// A service for UI related data model tasks + /// public interface IDataModelUIService : IArtemisSharedUIService { + /// + /// Gets a read-only list of all registered data model editors + /// IReadOnlyCollection RegisteredDataModelEditors { get; } - IReadOnlyCollection RegisteredDataModelDisplays { get; } - DataModelPropertiesViewModel GetMainDataModelVisualization(); - DataModelPropertiesViewModel GetPluginDataModelVisualization(Plugin plugin, bool includeMainDataModel); - DataModelVisualizationRegistration RegisterDataModelInput(PluginInfo pluginInfo, IReadOnlyCollection compatibleConversionTypes) where T : DataModelInputViewModel; - DataModelVisualizationRegistration RegisterDataModelDisplay(PluginInfo pluginInfo) where T : DataModelDisplayViewModel; + /// + /// Gets a read-only list of all registered data model displays + /// + IReadOnlyCollection RegisteredDataModelDisplays { get; } + + /// + /// Creates a data model visualization view model for the main data model + /// + /// + /// A data model visualization view model containing all data model expansions and modules that expand the main + /// data model + /// + DataModelPropertiesViewModel GetMainDataModelVisualization(); + + /// + /// Creates a data model visualization view model for the data model of the provided plugin feature + /// + /// The plugin feature to create hte data model visualization view model for + /// Whether or not also to include the main data model + /// A data model visualization view model containing the data model of the provided feature + DataModelPropertiesViewModel GetPluginDataModelVisualization(PluginFeature pluginFeature, bool includeMainDataModel); + + /// + /// Updates the children of the provided main data model visualization, removing disabled children and adding newly + /// enabled children + /// + void UpdateModules(DataModelPropertiesViewModel mainDataModelVisualization); + + /// + /// Registers a new data model editor + /// + /// The type of the editor + /// The plugin this editor belongs to + /// A collection of extra types this editor supports + /// A registration that can be used to remove the editor + DataModelVisualizationRegistration RegisterDataModelInput(Plugin plugin, IReadOnlyCollection compatibleConversionTypes) where T : DataModelInputViewModel; + + /// + /// Registers a new data model display + /// + /// The type of the display + /// The plugin this display belongs to + /// A registration that can be used to remove the display + DataModelVisualizationRegistration RegisterDataModelDisplay(Plugin plugin) where T : DataModelDisplayViewModel; + + /// + /// Removes a data model editor + /// + /// + /// The registration of the editor as returned by + /// void RemoveDataModelInput(DataModelVisualizationRegistration registration); + + /// + /// Removes a data model display + /// + /// + /// The registration of the display as returned by + /// void RemoveDataModelDisplay(DataModelVisualizationRegistration registration); + /// + /// Creates the most appropriate display view model for the provided that can display + /// a value + /// + /// The type of data model property to find a display view model for + /// The description of the data model property + /// + /// If , a simple .ToString() display view model will be + /// returned if nothing else is found + /// + /// The most appropriate display view model for the provided DataModelDisplayViewModel GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute description, bool fallBackToDefault = false); + + /// + /// Creates the most appropriate input view model for the provided that allows + /// inputting a value + /// + /// The type of data model property to find a display view model for + /// The description of the data model property + /// The initial value to show in the input + /// A function to call whenever the input was updated (submitted or not) + /// The most appropriate input view model for the provided DataModelInputViewModel GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute description, object initialValue, Action updateCallback); + /// + /// Creates a view model that allows selecting a value from the data model + /// + /// + /// A view model that allows selecting a value from the data model DataModelDynamicViewModel GetDynamicSelectionViewModel(Module module); + + /// + /// Creates a view model that allows entering a value matching the target data model type + /// + /// The type of data model property to allow input for + /// The description of the target data model property + /// A view model that allows entering a value matching the target data model type DataModelStaticViewModel GetStaticInputViewModel(Type targetType, DataModelPropertyAttribute targetDescription); } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs index 8cb46338d..92f712a02 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs @@ -71,9 +71,9 @@ namespace Artemis.UI.Shared.Services /// /// Note: Registration will remove itself on plugin disable so you don't have to /// - /// + /// /// - PropertyInputRegistration RegisterPropertyInput(PluginInfo pluginInfo) where T : PropertyInputViewModel; + PropertyInputRegistration RegisterPropertyInput(Plugin plugin) where T : PropertyInputViewModel; /// /// Registers a new property input view model used in the profile editor for the generic type defined in @@ -81,9 +81,9 @@ namespace Artemis.UI.Shared.Services /// Note: Registration will remove itself on plugin disable so you don't have to /// /// - /// + /// /// - PropertyInputRegistration RegisterPropertyInput(Type viewModelType, PluginInfo pluginInfo); + PropertyInputRegistration RegisterPropertyInput(Type viewModelType, Plugin plugin); void RemovePropertyInput(PropertyInputRegistration registration); diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index f50b80185..0dca8e593 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -177,12 +177,12 @@ namespace Artemis.UI.Shared.Services return true; } - public PropertyInputRegistration RegisterPropertyInput(PluginInfo pluginInfo) where T : PropertyInputViewModel + public PropertyInputRegistration RegisterPropertyInput(Plugin plugin) where T : PropertyInputViewModel { - return RegisterPropertyInput(typeof(T), pluginInfo); + return RegisterPropertyInput(typeof(T), plugin); } - public PropertyInputRegistration RegisterPropertyInput(Type viewModelType, PluginInfo pluginInfo) + public PropertyInputRegistration RegisterPropertyInput(Type viewModelType, Plugin plugin) { if (!typeof(PropertyInputViewModel).IsAssignableFrom(viewModelType)) throw new ArtemisSharedUIException($"Property input VM type must implement {nameof(PropertyInputViewModel)}"); @@ -201,13 +201,14 @@ namespace Artemis.UI.Shared.Services PropertyInputRegistration existing = _registeredPropertyEditors.FirstOrDefault(r => r.SupportedType == supportedType); if (existing != null) { - if (existing.PluginInfo != pluginInfo) - throw new ArtemisPluginException($"Cannot register property editor for type {supportedType.Name} because an editor was already registered by {existing.PluginInfo.Name}"); + if (existing.Plugin != plugin) + throw new ArtemisPluginException($"Cannot register property editor for type {supportedType.Name} because an editor was already " + + $"registered by {existing.Plugin}"); return existing; } Kernel.Bind(viewModelType).ToSelf(); - PropertyInputRegistration registration = new PropertyInputRegistration(this, pluginInfo, supportedType, viewModelType); + PropertyInputRegistration registration = new PropertyInputRegistration(this, plugin, supportedType, viewModelType); _registeredPropertyEditors.Add(registration); return registration; } @@ -281,7 +282,7 @@ namespace Artemis.UI.Shared.Services return null; ConstructorArgument parameter = new ConstructorArgument("layerProperty", layerProperty); - IKernel kernel = registration != null ? registration.PluginInfo.Kernel : Kernel; + IKernel kernel = registration != null ? registration.Plugin.Kernel : Kernel; return (PropertyInputViewModel) kernel.Get(viewModelType, parameter); } diff --git a/src/Artemis.UI.Shared/Utilities/PluginUtilities.cs b/src/Artemis.UI.Shared/Utilities/PluginUtilities.cs index 140b104bd..cdf75ad1c 100644 --- a/src/Artemis.UI.Shared/Utilities/PluginUtilities.cs +++ b/src/Artemis.UI.Shared/Utilities/PluginUtilities.cs @@ -14,13 +14,13 @@ namespace Artemis.UI.Shared /// /// Transforms the provided icon so that it is usable by the control /// - /// The info of the plugin the icon belongs to + /// The plugin the icon belongs to /// /// The icon, may be a string representation of a or a relative path /// pointing to a .svg file /// /// - public static object GetPluginIcon(PluginInfo pluginInfo, string icon) + public static object GetPluginIcon(Plugin plugin, string icon) { if (icon == null) return PackIconKind.QuestionMarkCircle; @@ -28,7 +28,7 @@ namespace Artemis.UI.Shared // Icon is provided as a path if (icon.EndsWith(".svg")) { - string iconPath = pluginInfo.ResolveRelativePath(icon); + string iconPath = plugin.ResolveRelativePath(icon); if (!File.Exists(iconPath)) return PackIconKind.QuestionMarkCircle; return iconPath; diff --git a/src/Artemis.UI.Shared/packages.lock.json b/src/Artemis.UI.Shared/packages.lock.json index 6f3d41d90..7df90fbc1 100644 --- a/src/Artemis.UI.Shared/packages.lock.json +++ b/src/Artemis.UI.Shared/packages.lock.json @@ -1300,7 +1300,6 @@ "Serilog.Sinks.Debug": "1.0.1", "Serilog.Sinks.File": "4.1.0", "SkiaSharp": "1.68.3", - "Stylet": "1.3.4", "System.Buffers": "4.5.0", "System.Numerics.Vectors": "4.5.0", "System.Reflection.Metadata": "1.8.0", diff --git a/src/Artemis.UI/Bootstrapper.cs b/src/Artemis.UI/Bootstrapper.cs index dbced1ec0..d64e10620 100644 --- a/src/Artemis.UI/Bootstrapper.cs +++ b/src/Artemis.UI/Bootstrapper.cs @@ -39,6 +39,8 @@ namespace Artemis.UI protected override void Launch() { + Core.Utilities.ShutdownRequested += UtilitiesOnShutdownRequested; + ILogger logger = Kernel.Get(); IViewManager viewManager = Kernel.Get(); @@ -86,7 +88,7 @@ namespace Artemis.UI protected override void ConfigureIoC(IKernel kernel) { - kernel.Settings.InjectNonPublic = true; + // kernel.Settings.InjectNonPublic = true; // Load the UI modules kernel.Load(); @@ -116,6 +118,11 @@ namespace Artemis.UI e.Handled = true; } + private void UtilitiesOnShutdownRequested(object? sender, EventArgs e) + { + Execute.OnUIThread(() => Application.Current.Shutdown()); + } + private void CreateDataDirectory(ILogger logger) { // Ensure the data folder exists diff --git a/src/Artemis.UI/DefaultTypes/DataModel/Input/DoubleDataModelInputView.xaml b/src/Artemis.UI/DefaultTypes/DataModel/Input/DoubleDataModelInputView.xaml index cd487c12e..003b7dee2 100644 --- a/src/Artemis.UI/DefaultTypes/DataModel/Input/DoubleDataModelInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/DataModel/Input/DoubleDataModelInputView.xaml @@ -16,7 +16,7 @@ LostFocus="{s:Action Submit}" Width="140"> - + \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/DataModel/Input/IntDataModelInputView.xaml b/src/Artemis.UI/DefaultTypes/DataModel/Input/IntDataModelInputView.xaml index eae99304e..41b49a299 100644 --- a/src/Artemis.UI/DefaultTypes/DataModel/Input/IntDataModelInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/DataModel/Input/IntDataModelInputView.xaml @@ -16,7 +16,7 @@ LostFocus="{s:Action Submit}" Width="140"> - + \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/DataModel/Input/StringDataModelInputView.xaml b/src/Artemis.UI/DefaultTypes/DataModel/Input/StringDataModelInputView.xaml index c93e9fbd1..5fefd4728 100644 --- a/src/Artemis.UI/DefaultTypes/DataModel/Input/StringDataModelInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/DataModel/Input/StringDataModelInputView.xaml @@ -10,7 +10,7 @@ d:DesignHeight="450" d:DesignWidth="800"> - + \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs index 33270c15a..9f4699243 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs @@ -11,17 +11,17 @@ namespace Artemis.UI.PropertyInput { public class BrushPropertyInputViewModel : PropertyInputViewModel { - private readonly IPluginService _pluginService; + private readonly IPluginManagementService _pluginManagementService; private BindableCollection _descriptors; public BrushPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, - IPluginService pluginService) : base(layerProperty, profileEditorService) + IPluginManagementService pluginManagementService) : base(layerProperty, profileEditorService) { - _pluginService = pluginService; + _pluginManagementService = pluginManagementService; - _pluginService.PluginEnabled += PluginServiceOnPluginLoaded; - _pluginService.PluginDisabled += PluginServiceOnPluginLoaded; + _pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginManagementLoaded; + _pluginManagementService.PluginDisabled += PluginManagementServiceOnPluginManagementLoaded; UpdateEnumValues(); } @@ -39,7 +39,7 @@ namespace Artemis.UI.PropertyInput public void UpdateEnumValues() { - List layerBrushProviders = _pluginService.GetPluginsOfType(); + List layerBrushProviders = _pluginManagementService.GetFeaturesOfType(); Descriptors = new BindableCollection(layerBrushProviders.SelectMany(l => l.LayerBrushDescriptors)); NotifyOfPropertyChange(nameof(SelectedDescriptor)); } @@ -47,8 +47,8 @@ namespace Artemis.UI.PropertyInput public override void Dispose() { - _pluginService.PluginEnabled -= PluginServiceOnPluginLoaded; - _pluginService.PluginDisabled -= PluginServiceOnPluginLoaded; + _pluginManagementService.PluginEnabled -= PluginManagementServiceOnPluginManagementLoaded; + _pluginManagementService.PluginDisabled -= PluginManagementServiceOnPluginManagementLoaded; base.Dispose(); } @@ -63,7 +63,7 @@ namespace Artemis.UI.PropertyInput InputValue = new LayerBrushReference(value); } - private void PluginServiceOnPluginLoaded(object sender, PluginEventArgs e) + private void PluginManagementServiceOnPluginManagementLoaded(object sender, PluginEventArgs e) { UpdateEnumValues(); } diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 28a691220..b73ccea26 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -36,6 +36,7 @@ namespace Artemis.UI.Ninject.Factories public interface ISettingsVmFactory : IVmFactory { PluginSettingsViewModel CreatePluginSettingsViewModel(Plugin plugin); + PluginFeatureViewModel CreatePluginFeatureViewModel(PluginFeature feature); DeviceSettingsViewModel CreateDeviceSettingsViewModel(ArtemisDevice device); } diff --git a/src/Artemis.UI/Ninject/PluginUIModule.cs b/src/Artemis.UI/Ninject/PluginUIModule.cs index 5cccc2033..d6d4da758 100644 --- a/src/Artemis.UI/Ninject/PluginUIModule.cs +++ b/src/Artemis.UI/Ninject/PluginUIModule.cs @@ -4,25 +4,31 @@ using Artemis.UI.Stylet; using FluentValidation; using Ninject.Extensions.Conventions; using Ninject.Modules; +using Ninject.Planning.Bindings.Resolvers; using Stylet; namespace Artemis.UI.Ninject { public class PluginUIModule : NinjectModule { - public PluginUIModule(PluginInfo pluginInfo) + public PluginUIModule(Plugin plugin) { - PluginInfo = pluginInfo ?? throw new ArgumentNullException(nameof(pluginInfo)); + Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin)); } - public PluginInfo PluginInfo { get; } + public Plugin Plugin { get; } public override void Load() { + if (Kernel == null) + throw new ArgumentNullException("Kernel shouldn't be null here."); + + Kernel.Components.Add(); Bind(typeof(IModelValidator<>)).To(typeof(FluentValidationAdapter<>)); + Kernel.Bind(x => { - x.From(PluginInfo.Assembly) + x.From(Plugin.Assembly) .SelectAllClasses() .InheritedFrom() .BindAllInterfaces(); diff --git a/src/Artemis.UI/Ninject/UiModule.cs b/src/Artemis.UI/Ninject/UiModule.cs index 9bfa0a102..13ead7e67 100644 --- a/src/Artemis.UI/Ninject/UiModule.cs +++ b/src/Artemis.UI/Ninject/UiModule.cs @@ -3,6 +3,7 @@ using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.InstanceProviders; using Artemis.UI.Screens; using Artemis.UI.Screens.ProfileEditor; +using Artemis.UI.Screens.Splash; using Artemis.UI.Services.Interfaces; using Artemis.UI.Shared.Services; using Artemis.UI.Stylet; @@ -10,6 +11,7 @@ using FluentValidation; using Ninject.Extensions.Conventions; using Ninject.Extensions.Factory; using Ninject.Modules; +using Ninject.Planning.Bindings.Resolvers; using Stylet; namespace Artemis.UI.Ninject @@ -21,6 +23,11 @@ namespace Artemis.UI.Ninject if (Kernel == null) throw new ArgumentNullException("Kernel shouldn't be null here."); + Kernel.Components.Add(); + + Kernel.Bind().ToSelf().InSingletonScope(); + Kernel.Bind().ToSelf(); + // Bind all built-in VMs Kernel.Bind(x => { diff --git a/src/Artemis.UI/Ninject/ViewsSelfBindingResolver.cs b/src/Artemis.UI/Ninject/ViewsSelfBindingResolver.cs new file mode 100644 index 000000000..87e9a45f9 --- /dev/null +++ b/src/Artemis.UI/Ninject/ViewsSelfBindingResolver.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using Ninject.Activation; +using Ninject.Activation.Providers; +using Ninject.Components; +using Ninject.Infrastructure; +using Ninject.Planning; +using Ninject.Planning.Bindings; +using Ninject.Planning.Bindings.Resolvers; +using Ninject.Selection.Heuristics; + +namespace Artemis.UI.Ninject +{ + /// + /// Represents a binding resolver that use the service in question itself as the target to activate but only if the service is a . + /// + public class UIElementSelfBindingResolver : NinjectComponent, IMissingBindingResolver + { + private readonly IConstructorScorer constructorScorer; + private readonly IPlanner planner; + + /// + /// Initializes a new instance of the class. + /// + /// The component. + /// The component. + public UIElementSelfBindingResolver(IPlanner planner, IConstructorScorer constructorScorer) + { + this.planner = planner; + this.constructorScorer = constructorScorer; + } + + /// + /// Returns a value indicating whether the specified service is self-bindable. + /// + /// The service. + /// + /// if the type is self-bindable; otherwise, . + /// + protected virtual bool TypeIsSelfBindable(Type service) + { + return !service.IsInterface + && !service.IsAbstract + && !service.IsValueType + && service != typeof(string) + && !service.ContainsGenericParameters; + } + + /// + /// Returns any bindings from the specified collection that match the specified service. + /// + /// The dictionary of all registered bindings. + /// The service in question. + /// + /// The series of matching bindings. + /// + public IEnumerable Resolve(Multimap bindings, IRequest request) + { + Type service = request.Service; + + if (!TypeIsSelfBindable(service) || service.IsAssignableFrom(typeof(UIElement))) + return Enumerable.Empty(); + + return new[] + { + new Binding(service) + { + ProviderCallback = ctx => new StandardProvider(service, planner, constructorScorer) + } + }; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Modules/ModuleRootViewModel.cs b/src/Artemis.UI/Screens/Modules/ModuleRootViewModel.cs index 1986f77b0..90b7e1330 100644 --- a/src/Artemis.UI/Screens/Modules/ModuleRootViewModel.cs +++ b/src/Artemis.UI/Screens/Modules/ModuleRootViewModel.cs @@ -2,6 +2,7 @@ using System.Linq; using Artemis.Core.Modules; using Artemis.UI.Ninject.Factories; +using Artemis.UI.Shared.Modules; using Ninject; using Ninject.Parameters; using Stylet; @@ -45,7 +46,7 @@ namespace Artemis.UI.Screens.Modules ConstructorArgument module = new ConstructorArgument("module", Module); ConstructorArgument displayName = new ConstructorArgument("displayName", DisplayName); - ModuleViewModel viewModel = (ModuleViewModel) Module.PluginInfo.Kernel.Get(moduleTab.Type, module, displayName); + ModuleViewModel viewModel = (ModuleViewModel) Module.Plugin.Kernel.Get(moduleTab.Type, module, displayName); Items.Add(viewModel); } } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs index 1abc15d2c..c3e43e965 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs @@ -12,13 +12,13 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects { public class EffectsViewModel : Conductor.Collection.AllActive { - private readonly IPluginService _pluginService; + private readonly IPluginManagementService _pluginManagementService; private readonly IProfileEditorService _profileEditorService; private LayerEffectDescriptor _selectedLayerEffectDescriptor; - public EffectsViewModel(LayerPropertiesViewModel layerPropertiesViewModel, IPluginService pluginService, IProfileEditorService profileEditorService) + public EffectsViewModel(LayerPropertiesViewModel layerPropertiesViewModel, IPluginManagementService pluginManagementService, IProfileEditorService profileEditorService) { - _pluginService = pluginService; + _pluginManagementService = pluginManagementService; _profileEditorService = profileEditorService; LayerPropertiesViewModel = layerPropertiesViewModel; PropertyChanged += HandleSelectedLayerEffectChanged; @@ -35,7 +35,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects public void PopulateDescriptors() { - List layerBrushProviders = _pluginService.GetPluginsOfType(); + List layerBrushProviders = _pluginManagementService.GetFeaturesOfType(); List descriptors = layerBrushProviders.SelectMany(l => l.LayerEffectDescriptors).ToList(); Items.AddRange(descriptors.Except(Items)); Items.RemoveRange(Items.Except(descriptors)); diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs index 48d37fe72..a78c57aae 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs @@ -9,6 +9,8 @@ using Artemis.Core.LayerEffects; using Artemis.UI.Exceptions; using Artemis.UI.Screens.ProfileEditor.Dialogs; using Artemis.UI.Screens.ProfileEditor.Windows; +using Artemis.UI.Shared.LayerBrushes; +using Artemis.UI.Shared.LayerEffects; using Artemis.UI.Shared.Services; using Ninject; using Ninject.Parameters; @@ -18,6 +20,15 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree { public class TreeGroupViewModel : PropertyChangedBase { + public enum LayerPropertyGroupType + { + General, + Transform, + LayerBrushRoot, + LayerEffectRoot, + None + } + private readonly IDialogService _dialogService; private readonly IKernel _kernel; private readonly IProfileEditorService _profileEditorService; @@ -55,7 +66,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree public void OpenBrushSettings() { BaseLayerBrush layerBrush = LayerPropertyGroup.LayerBrush; - LayerBrushConfigurationDialog configurationViewModel = layerBrush.ConfigurationDialog; + LayerBrushConfigurationDialog configurationViewModel = (LayerBrushConfigurationDialog) layerBrush.ConfigurationDialog; if (configurationViewModel == null) return; @@ -69,7 +80,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree // Find the BaseLayerBrush parameter, it is required by the base constructor so its there for sure ParameterInfo brushParameter = constructors.First().GetParameters().First(p => typeof(BaseLayerBrush).IsAssignableFrom(p.ParameterType)); ConstructorArgument argument = new ConstructorArgument(brushParameter.Name, layerBrush); - BrushConfigurationViewModel viewModel = (BrushConfigurationViewModel) layerBrush.PluginInfo.Kernel.Get(configurationViewModel.Type, argument); + BrushConfigurationViewModel viewModel = (BrushConfigurationViewModel) layerBrush.Descriptor.Provider.Plugin.Kernel.Get(configurationViewModel.Type, argument); _windowManager.ShowDialog(new LayerBrushSettingsWindowViewModel(viewModel)); } @@ -82,7 +93,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree public void OpenEffectSettings() { BaseLayerEffect layerEffect = LayerPropertyGroup.LayerEffect; - LayerEffectConfigurationDialog configurationViewModel = layerEffect.ConfigurationDialog; + LayerEffectConfigurationDialog configurationViewModel = (LayerEffectConfigurationDialog) layerEffect.ConfigurationDialog; if (configurationViewModel == null) return; @@ -95,7 +106,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree ParameterInfo effectParameter = constructors.First().GetParameters().First(p => typeof(BaseLayerEffect).IsAssignableFrom(p.ParameterType)); ConstructorArgument argument = new ConstructorArgument(effectParameter.Name, layerEffect); - EffectConfigurationViewModel viewModel = (EffectConfigurationViewModel) layerEffect.PluginInfo.Kernel.Get(configurationViewModel.Type, argument); + EffectConfigurationViewModel viewModel = (EffectConfigurationViewModel) layerEffect.Descriptor.Provider.Plugin.Kernel.Get(configurationViewModel.Type, argument); _windowManager.ShowDialog(new LayerEffectSettingsWindowViewModel(viewModel)); } catch (Exception e) @@ -169,14 +180,5 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree else GroupType = LayerPropertyGroupType.None; } - - public enum LayerPropertyGroupType - { - General, - Transform, - LayerBrushRoot, - LayerEffectRoot, - None - } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs index 6451833b4..84cecbe66 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Artemis.Core; +using Artemis.Core.LayerBrushes; using Artemis.Core.Services; using Artemis.UI.Exceptions; using Artemis.UI.Ninject.Factories; @@ -139,7 +140,12 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem throw new ArtemisUIException("Cannot add a layer to a profile element of type " + ProfileElement.GetType().Name); Layer layer = new Layer(ProfileElement, "New layer"); - layer.ChangeLayerBrush(_layerBrushService.GetDefaultLayerBrush()); + + // Could be null if the default brush got disabled + LayerBrushDescriptor? brush = _layerBrushService.GetDefaultLayerBrush(); + if (brush != null) + layer.ChangeLayerBrush(brush); + layer.AddLeds(_surfaceService.ActiveSurface.Devices.SelectMany(d => d.Leds)); _profileEditorService.UpdateSelectedProfile(); _profileEditorService.ChangeSelectedProfileElement(layer); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs index b4481817c..cf09e35ec 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Windows; using System.Windows.Input; using Artemis.Core; +using Artemis.Core.LayerBrushes; using Artemis.Core.Services; using Artemis.UI.Properties; using Artemis.UI.Shared.Services; @@ -91,7 +92,11 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools private void CreateLayer(Folder folder, List selectedLeds) { Layer newLayer = new Layer(folder, "New layer"); - newLayer.ChangeLayerBrush(_layerBrushService.GetDefaultLayerBrush()); + + LayerBrushDescriptor brush = _layerBrushService.GetDefaultLayerBrush(); + if (brush != null) + newLayer.ChangeLayerBrush(brush); + newLayer.AddLeds(selectedLeds); ProfileEditorService.ChangeSelectedProfileElement(newLayer); ProfileEditorService.UpdateSelectedProfileElement(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowViewModel.cs index a1601ce90..682be6025 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowViewModel.cs @@ -1,5 +1,5 @@ using System; -using Artemis.Core.LayerBrushes; +using Artemis.UI.Shared.LayerBrushes; using Stylet; namespace Artemis.UI.Screens.ProfileEditor.Windows diff --git a/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowViewModel.cs index cf759dde2..6d2438bdf 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowViewModel.cs @@ -1,5 +1,5 @@ using System; -using Artemis.Core.LayerEffects; +using Artemis.UI.Shared.LayerEffects; using Stylet; namespace Artemis.UI.Screens.ProfileEditor.Windows diff --git a/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugViewModel.cs index 713df7436..f5b534518 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugViewModel.cs @@ -55,7 +55,7 @@ namespace Artemis.UI.Screens.Settings.Debug { try { - Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Device.Plugin.PluginInfo.Directory.FullName); + Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Device.PluginFeature.Plugin.Directory.FullName); } catch (Exception e) { diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml index f04946b2f..9c7d9b87f 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml @@ -8,10 +8,11 @@ xmlns:wpf="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:modules="clr-namespace:Artemis.Core.Modules;assembly=Artemis.Core" xmlns:dataModel="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" + xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:DataModelDebugViewModel}"> - + @@ -43,19 +44,11 @@ wpf:HintAssist.Hint="Select a module" IsEditable="True" TextSearch.TextPath="DisplayName" + DisplayMemberPath="DisplayName" Margin="5 0 0 0" IsEnabled="{Binding IsModuleFilterEnabled}" SelectedItem="{Binding SelectedModule}" - ItemsSource="{Binding Modules}"> - - - - - - - - - + ItemsSource="{Binding Modules}" /> @@ -73,12 +66,12 @@ [] - + - + diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs index 8d7e501ac..c818e3cfd 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs @@ -13,7 +13,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs public class DataModelDebugViewModel : Screen { private readonly IDataModelUIService _dataModelUIService; - private readonly IPluginService _pluginService; + private readonly IPluginManagementService _pluginManagementService; private readonly Timer _updateTimer; private bool _isModuleFilterEnabled; private DataModelPropertiesViewModel _mainDataModel; @@ -21,13 +21,14 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs private string _propertySearch; private Module _selectedModule; - public DataModelDebugViewModel(IDataModelUIService dataModelUIService, IPluginService pluginService) + public DataModelDebugViewModel(IDataModelUIService dataModelUIService, IPluginManagementService pluginManagementService) { _dataModelUIService = dataModelUIService; - _pluginService = pluginService; + _pluginManagementService = pluginManagementService; _updateTimer = new Timer(500); DisplayName = "Data model"; + Modules = new BindableCollection(); } public DataModelPropertiesViewModel MainDataModel @@ -42,11 +43,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs set => SetAndNotify(ref _propertySearch, value); } - public List Modules - { - get => _modules; - set => SetAndNotify(ref _modules, value); - } + public BindableCollection Modules { get; } public Module SelectedModule { @@ -77,8 +74,8 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs GetDataModel(); _updateTimer.Start(); _updateTimer.Elapsed += OnUpdateTimerOnElapsed; - _pluginService.PluginEnabled += PluginServiceOnPluginToggled; - _pluginService.PluginDisabled += PluginServiceOnPluginToggled; + _pluginManagementService.PluginFeatureEnabled += OnPluginFeatureToggled; + _pluginManagementService.PluginFeatureDisabled += OnPluginFeatureToggled; PopulateModules(); @@ -89,14 +86,23 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs { _updateTimer.Stop(); _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; - _pluginService.PluginEnabled -= PluginServiceOnPluginToggled; - _pluginService.PluginDisabled -= PluginServiceOnPluginToggled; + _pluginManagementService.PluginFeatureEnabled -= OnPluginFeatureToggled; + _pluginManagementService.PluginFeatureDisabled -= OnPluginFeatureToggled; base.OnDeactivate(); } + private void OnPluginFeatureToggled(object sender, PluginFeatureEventArgs e) + { + if (e.PluginFeature is DataModelPluginFeature) + PopulateModules(); + } + private void OnUpdateTimerOnElapsed(object sender, ElapsedEventArgs args) { + if (MainDataModel == null) + return; + lock (MainDataModel) { MainDataModel.Update(_dataModelUIService, null); @@ -110,14 +116,15 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs : _dataModelUIService.GetMainDataModelVisualization(); } - private void PluginServiceOnPluginToggled(object? sender, PluginEventArgs e) - { - PopulateModules(); - } - private void PopulateModules() { - Modules = _pluginService.GetPluginsOfType().Where(p => p.Enabled).ToList(); + Modules.Clear(); + Modules.AddRange(_pluginManagementService.GetFeaturesOfType().Where(p => p.IsEnabled).OrderBy(m => m.DisplayName)); + + if (SelectedModule == null) + _dataModelUIService.UpdateModules(MainDataModel); + else if (!SelectedModule.IsEnabled) + SelectedModule = null; } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsViewModel.cs index aff26a83d..a89933790 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsViewModel.cs @@ -60,7 +60,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices { try { - Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Device.Plugin.PluginInfo.Directory.FullName); + Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Device.PluginFeature.Plugin.Directory.FullName); } catch (Exception e) { diff --git a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs index 5760fd78f..06c65f3c4 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs @@ -25,7 +25,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.General private List> _targetFrameRates; private readonly PluginSetting _defaultLayerBrushDescriptor; - public GeneralSettingsTabViewModel(IDialogService dialogService, IDebugService debugService, ISettingsService settingsService, IPluginService pluginService) + public GeneralSettingsTabViewModel(IDialogService dialogService, IDebugService debugService, ISettingsService settingsService, IPluginManagementService pluginManagementService) { DisplayName = "GENERAL"; @@ -46,12 +46,12 @@ namespace Artemis.UI.Screens.Settings.Tabs.General // Anything else is kinda broken right now SampleSizes = new List {1, 9}; - List layerBrushProviders = pluginService.GetPluginsOfType(); + List layerBrushProviders = pluginManagementService.GetFeaturesOfType(); LayerBrushDescriptors = new BindableCollection(layerBrushProviders.SelectMany(l => l.LayerBrushDescriptors)); _defaultLayerBrushDescriptor = _settingsService.GetSetting("ProfileEditor.DefaultLayerBrushDescriptor", new LayerBrushReference { - BrushPluginGuid = Guid.Parse("92a9d6ba-6f7a-4937-94d5-c1d715b4141a"), + LayerBrushProviderId = "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba", BrushType = "ColorBrush" }); } diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Modules/ModuleOrderTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Modules/ModuleOrderTabViewModel.cs index 7e1452e6d..89c1335ec 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Modules/ModuleOrderTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Modules/ModuleOrderTabViewModel.cs @@ -10,26 +10,35 @@ namespace Artemis.UI.Screens.Settings.Tabs.Modules public class ModuleOrderTabViewModel : Screen, IDropTarget { private readonly IModuleService _moduleService; - private readonly IPluginService _pluginService; + private readonly IPluginManagementService _pluginManagementService; private readonly DefaultDropHandler _defaultDropHandler; - private readonly List _modules; + private List _modules; - public ModuleOrderTabViewModel(IPluginService pluginService, IModuleService moduleService) + public ModuleOrderTabViewModel(IPluginManagementService pluginManagementService, IModuleService moduleService) { DisplayName = "MODULE PRIORITY"; - _pluginService = pluginService; + _pluginManagementService = pluginManagementService; _moduleService = moduleService; - _modules = new List(pluginService.GetPluginsOfType().Select(m => new ModuleOrderModuleViewModel(m))); _defaultDropHandler = new DefaultDropHandler(); NormalModules = new BindableCollection(); ApplicationModules = new BindableCollection(); OverlayModules = new BindableCollection(); + } + protected override void OnActivate() + { + base.OnActivate(); Update(); } + protected override void OnDeactivate() + { + base.OnDeactivate(); + _modules = null; + } + public BindableCollection NormalModules { get; set; } public BindableCollection ApplicationModules { get; set; } public BindableCollection OverlayModules { get; set; } @@ -67,6 +76,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.Modules public void Update() { + if (_modules == null) + _modules = _pluginManagementService.GetFeaturesOfType().Select(m => new ModuleOrderModuleViewModel(m)).ToList(); NormalModules.Clear(); NormalModules.AddRange(_modules.Where(m => m.Module.PriorityCategory == ModulePriorityCategory.Normal).OrderBy(m => m.Module.Priority)); diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml new file mode 100644 index 000000000..9e88a2346 --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + Feature enabled + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs new file mode 100644 index 000000000..11313831d --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs @@ -0,0 +1,166 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using Artemis.Core; +using Artemis.Core.DataModelExpansions; +using Artemis.Core.DeviceProviders; +using Artemis.Core.LayerBrushes; +using Artemis.Core.LayerEffects; +using Artemis.Core.Modules; +using Artemis.Core.Services; +using Artemis.UI.Shared.Services; +using Humanizer; +using MaterialDesignThemes.Wpf; +using Stylet; + +namespace Artemis.UI.Screens.Settings.Tabs.Plugins +{ + public class PluginFeatureViewModel : Screen + { + private readonly IDialogService _dialogService; + private readonly IPluginManagementService _pluginManagementService; + private readonly ISnackbarMessageQueue _snackbarMessageQueue; + private bool _enabling; + + public PluginFeatureViewModel(PluginFeature feature, + IDialogService dialogService, + IPluginManagementService pluginManagementService, + ISnackbarMessageQueue snackbarMessageQueue) + { + _dialogService = dialogService; + _pluginManagementService = pluginManagementService; + _snackbarMessageQueue = snackbarMessageQueue; + + Feature = feature; + Icon = GetIconKind(); + } + + public PluginFeature Feature { get; } + public PackIconKind Icon { get; } + + public string Name => Feature.GetType().Name.Humanize(); + + public Exception LoadException => Feature.LoadException; + + public bool Enabling + { + get => _enabling; + set => SetAndNotify(ref _enabling, value); + } + + public bool IsEnabled + { + get => Feature.IsEnabled; + set => Task.Run(() => UpdateEnabled(value)); + } + + public void ShowLogsFolder() + { + try + { + Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs")); + } + catch (Exception e) + { + _dialogService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e); + } + } + + public void ViewLoadException() + { + if (LoadException == null) + return; + + _dialogService.ShowExceptionDialog("Feature failed to enable", Feature.LoadException); + } + + protected override void OnInitialActivate() + { + base.OnInitialActivate(); + _pluginManagementService.PluginFeatureEnabling += OnFeatureEnabling; + _pluginManagementService.PluginFeatureEnabled += OnFeatureEnableStopped; + _pluginManagementService.PluginFeatureEnableFailed += OnFeatureEnableStopped; + } + + protected override void OnClose() + { + base.OnClose(); + _pluginManagementService.PluginFeatureEnabling -= OnFeatureEnabling; + _pluginManagementService.PluginFeatureEnabled -= OnFeatureEnableStopped; + _pluginManagementService.PluginFeatureEnableFailed -= OnFeatureEnableStopped; + } + + private async Task UpdateEnabled(bool enable) + { + if (IsEnabled == enable) + { + NotifyOfPropertyChange(nameof(IsEnabled)); + return; + } + + if (enable) + { + Enabling = true; + + try + { + await Task.Run(() => _pluginManagementService.EnablePluginFeature(Feature, true)); + } + catch (Exception e) + { + _snackbarMessageQueue.Enqueue($"Failed to enable {Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder); + } + finally + { + Enabling = false; + } + } + else + { + _pluginManagementService.DisablePluginFeature(Feature, true); + NotifyOfPropertyChange(nameof(IsEnabled)); + } + } + + private PackIconKind GetIconKind() + { + switch (Feature) + { + case BaseDataModelExpansion _: + return PackIconKind.TableAdd; + case DeviceProvider _: + return PackIconKind.Devices; + case ProfileModule _: + return PackIconKind.VectorRectangle; + case Module _: + return PackIconKind.GearBox; + case LayerBrushProvider _: + return PackIconKind.Brush; + case LayerEffectProvider _: + return PackIconKind.AutoAwesome; + } + + return PackIconKind.Plugin; + } + + #region Event handlers + + private void OnFeatureEnabling(object? sender, PluginFeatureEventArgs e) + { + if (e.PluginFeature != Feature) return; + Enabling = true; + } + + private void OnFeatureEnableStopped(object? sender, PluginFeatureEventArgs e) + { + if (e.PluginFeature != Feature) return; + Enabling = false; + + NotifyOfPropertyChange(nameof(IsEnabled)); + NotifyOfPropertyChange(nameof(LoadException)); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabView.xaml index efc2bd56d..13e5b8fc1 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabView.xaml @@ -9,10 +9,30 @@ mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:PluginSettingsTabViewModel}"> - - - The list below shows all loaded plugins. If you're missing something, view your logs folder. - + + + + + + + + + + + + + The list below shows all loaded plugins. If you're missing something, view your logs folder. + + + + + + @@ -24,6 +44,8 @@ - - + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs index 9693c7d51..ffb530cd3 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Artemis.Core.Services; @@ -9,29 +10,55 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins { public class PluginSettingsTabViewModel : Conductor.Collection.AllActive { - private readonly IPluginService _pluginService; + private readonly IPluginManagementService _pluginManagementService; private readonly ISettingsVmFactory _settingsVmFactory; - private BindableCollection _plugins; + private string _searchPluginInput; + private List _instances; - public PluginSettingsTabViewModel(IPluginService pluginService, ISettingsVmFactory settingsVmFactory) + public PluginSettingsTabViewModel(IPluginManagementService pluginManagementService, ISettingsVmFactory settingsVmFactory) { DisplayName = "PLUGINS"; - _pluginService = pluginService; + _pluginManagementService = pluginManagementService; _settingsVmFactory = settingsVmFactory; } + public string SearchPluginInput + { + get => _searchPluginInput; + set + { + if (!SetAndNotify(ref _searchPluginInput, value)) return; + UpdatePluginSearch(); + } + } + + private void UpdatePluginSearch() + { + if (_instances == null) + return; + + Items.Clear(); + + if (string.IsNullOrWhiteSpace(SearchPluginInput)) + Items.AddRange(_instances); + else + Items.AddRange(_instances.Where(i => i.Plugin.Info.Name.Contains(SearchPluginInput, StringComparison.OrdinalIgnoreCase) || + i.Plugin.Info.Description.Contains(SearchPluginInput, StringComparison.OrdinalIgnoreCase))); + } + protected override void OnActivate() { // Take it off the UI thread to avoid freezing on tab change Task.Run(async () => { - Items.Clear(); await Task.Delay(200); + _instances = _pluginManagementService.GetAllPlugins() + .Select(p => _settingsVmFactory.CreatePluginSettingsViewModel(p)) + .OrderBy(i => i.Plugin.Info.Name) + .ToList(); - List instances = _pluginService.GetAllPluginInfo().Select(p => _settingsVmFactory.CreatePluginSettingsViewModel(p.Instance)).ToList(); - foreach (PluginSettingsViewModel pluginSettingsViewModel in instances) - Items.Add(pluginSettingsViewModel); + UpdatePluginSearch(); }); base.OnActivate(); diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml index 0a5b1baf8..2ce5c47b2 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml @@ -6,17 +6,21 @@ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:s="https://github.com/canton7/Stylet" xmlns:devices="clr-namespace:Artemis.UI.Screens.Settings.Tabs.Plugins" + xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared" + xmlns:b="http://schemas.microsoft.com/xaml/behaviors" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" d:DataContext="{d:DesignInstance devices:PluginSettingsViewModel}" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + - + + - + @@ -24,64 +28,94 @@ - - + - - - + - - - - - - Plugin enabled - - - - - + + + + + Plugin enabled + + + + + + + + + + + Plugin features + + Enable the plugin to view its features + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs index 4887fdbc0..8029aa330 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs @@ -2,52 +2,44 @@ using System.Diagnostics; using System.IO; using System.Linq; -using System.Reflection; using System.Threading.Tasks; using Artemis.Core; -using Artemis.Core.DataModelExpansions; -using Artemis.Core.DeviceProviders; -using Artemis.Core.LayerBrushes; -using Artemis.Core.LayerEffects; -using Artemis.Core.Modules; using Artemis.Core.Services; -using Artemis.UI.Exceptions; +using Artemis.UI.Ninject.Factories; +using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using MaterialDesignThemes.Wpf; using Ninject; -using Ninject.Parameters; -using Serilog; using Stylet; -using Module = Artemis.Core.Modules.Module; namespace Artemis.UI.Screens.Settings.Tabs.Plugins { - public class PluginSettingsViewModel : PropertyChangedBase + public class PluginSettingsViewModel : Conductor.Collection.AllActive { private readonly IDialogService _dialogService; - private readonly ILogger _logger; - private readonly IPluginService _pluginService; + private readonly IPluginManagementService _pluginManagementService; + private readonly ISettingsVmFactory _settingsVmFactory; private readonly ISnackbarMessageQueue _snackbarMessageQueue; private readonly IWindowManager _windowManager; private bool _enabling; private Plugin _plugin; - private PluginInfo _pluginInfo; public PluginSettingsViewModel(Plugin plugin, - ILogger logger, + ISettingsVmFactory settingsVmFactory, IWindowManager windowManager, IDialogService dialogService, - IPluginService pluginService, + IPluginManagementService pluginManagementService, ISnackbarMessageQueue snackbarMessageQueue) { Plugin = plugin; - PluginInfo = plugin.PluginInfo; - _logger = logger; + _settingsVmFactory = settingsVmFactory; _windowManager = windowManager; _dialogService = dialogService; - _pluginService = pluginService; + _pluginManagementService = pluginManagementService; _snackbarMessageQueue = snackbarMessageQueue; + + Icon = PluginUtilities.GetPluginIcon(Plugin, Plugin.Info.Icon); } public Plugin Plugin @@ -56,46 +48,31 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins set => SetAndNotify(ref _plugin, value); } - public PluginInfo PluginInfo - { - get => _pluginInfo; - set => SetAndNotify(ref _pluginInfo, value); - } - public bool Enabling { get => _enabling; set => SetAndNotify(ref _enabling, value); } - public PackIconKind Icon => GetIconKind(); + public object Icon { get; set; } public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name; public bool CanOpenSettings => IsEnabled && Plugin.ConfigurationDialog != null; - public bool DisplayLoadFailed => !Enabling && PluginInfo.LoadException != null; - public bool RequiresRestart => Plugin.Enabled && !PluginInfo.Enabled; public bool IsEnabled { - get => Plugin.PluginInfo.Enabled; + get => Plugin.IsEnabled; set => Task.Run(() => UpdateEnabled(value)); } public void OpenSettings() { - PluginConfigurationDialog configurationViewModel = Plugin.ConfigurationDialog; + PluginConfigurationDialog configurationViewModel = (PluginConfigurationDialog) Plugin.ConfigurationDialog; if (configurationViewModel == null) return; try { - // Limit to one constructor, there's no need to have more and it complicates things anyway - ConstructorInfo[] constructors = configurationViewModel.Type.GetConstructors(); - if (constructors.Length != 1) - throw new ArtemisUIException("Plugin configuration dialogs must have exactly one constructor"); - - ParameterInfo pluginParameter = constructors.First().GetParameters().First(p => typeof(Plugin).IsAssignableFrom(p.ParameterType)); - ConstructorArgument plugin = new ConstructorArgument(pluginParameter.Name, Plugin); - PluginConfigurationViewModel viewModel = (PluginConfigurationViewModel) PluginInfo.Kernel.Get(configurationViewModel.Type, plugin); + PluginConfigurationViewModel viewModel = (PluginConfigurationViewModel) Plugin.Kernel.Get(configurationViewModel.Type); _windowManager.ShowDialog(new PluginSettingsWindowViewModel(viewModel, Icon)); } catch (Exception e) @@ -117,49 +94,22 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins } } - public void ShowLoadException() + protected override void OnInitialActivate() { - if (PluginInfo.LoadException == null) - return; + Plugin.FeatureAdded += PluginOnFeatureAdded; + Plugin.FeatureRemoved += PluginOnFeatureRemoved; + foreach (PluginFeature pluginFeature in Plugin.Features) + Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeature)); - _dialogService.ShowExceptionDialog("The plugin failed to load: " + PluginInfo.LoadException.Message, PluginInfo.LoadException); + base.OnInitialActivate(); } - public async Task Restart() + protected override void OnClose() { - _logger.Debug("Restarting for device provider disable {pluginInfo}", Plugin.PluginInfo); + Plugin.FeatureAdded -= PluginOnFeatureAdded; + Plugin.FeatureRemoved -= PluginOnFeatureRemoved; - // Give the logger a chance to write, might not always be enough but oh well - await Task.Delay(500); - Core.Utilities.Shutdown(2, true); - } - - private PackIconKind GetIconKind() - { - if (PluginInfo.Icon != null) - { - bool parsedIcon = Enum.TryParse(PluginInfo.Icon, true, out PackIconKind iconEnum); - if (parsedIcon) - return iconEnum; - } - - switch (Plugin) - { - case BaseDataModelExpansion _: - return PackIconKind.TableAdd; - case DeviceProvider _: - return PackIconKind.Devices; - case ProfileModule _: - return PackIconKind.VectorRectangle; - case Module _: - return PackIconKind.GearBox; - case LayerBrushProvider _: - return PackIconKind.Brush; - case LayerEffectProvider _: - return PackIconKind.AutoAwesome; - } - - return PackIconKind.Plugin; + base.OnClose(); } private async Task UpdateEnabled(bool enable) @@ -170,24 +120,17 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins return; } - if (!enable && Plugin is DeviceProvider) - { - await DisableDeviceProvider(); - return; - } - if (enable) { Enabling = true; - NotifyOfPropertyChange(nameof(DisplayLoadFailed)); try { - _pluginService.EnablePlugin(Plugin); + await Task.Run(() => _pluginManagementService.EnablePlugin(Plugin, true)); } catch (Exception e) { - _snackbarMessageQueue.Enqueue($"Failed to enable plugin {PluginInfo.Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder); + _snackbarMessageQueue.Enqueue($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder); } finally { @@ -195,44 +138,24 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins } } else - _pluginService.DisablePlugin(Plugin); + { + _pluginManagementService.DisablePlugin(Plugin, true); + } NotifyOfPropertyChange(nameof(IsEnabled)); NotifyOfPropertyChange(nameof(CanOpenSettings)); - NotifyOfPropertyChange(nameof(RequiresRestart)); - NotifyOfPropertyChange(nameof(DisplayLoadFailed)); } - private async Task DisableDeviceProvider() + private void PluginOnFeatureRemoved(object? sender, PluginFeatureEventArgs e) { - bool restart = false; + PluginFeatureViewModel viewModel = Items.FirstOrDefault(i => i.Feature == e.PluginFeature); + if (viewModel != null) + Items.Remove(viewModel); + } - // If any plugin already requires a restart, don't ask the user again - bool restartQueued = _pluginService.GetAllPluginInfo().Any(p => p.Instance != null && !p.Enabled && p.Instance.Enabled); - // If the plugin isn't enabled (load failed), it can be disabled without a restart - if (!restartQueued && Plugin.Enabled) - { - restart = await _dialogService.ShowConfirmDialog( - "Disable device provider", - "You are disabling a device provider, Artemis has to restart to \r\nfully disable this type of plugin", - "Restart now", - "Restart later" - ); - } - - _pluginService.DisablePlugin(Plugin); - if (restart) - { - _logger.Debug("Restarting for device provider disable {pluginInfo}", Plugin.PluginInfo); - - // Give the logger a chance to write, might not always be enough but oh well - await Task.Delay(500); - Core.Utilities.Shutdown(2, true); - } - - NotifyOfPropertyChange(nameof(IsEnabled)); - NotifyOfPropertyChange(nameof(RequiresRestart)); - NotifyOfPropertyChange(nameof(DisplayLoadFailed)); + private void PluginOnFeatureAdded(object? sender, PluginFeatureEventArgs e) + { + Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(e.PluginFeature)); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsWindowView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsWindowView.xaml index c9c7d866a..f8bbfa6a6 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsWindowView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsWindowView.xaml @@ -23,7 +23,7 @@ Identifier="PluginSettingsDialog" DialogTheme="Inherit"> - + diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsWindowViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsWindowViewModel.cs index 4693ebba6..7f4a229f8 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsWindowViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsWindowViewModel.cs @@ -1,6 +1,5 @@ using System; -using Artemis.Core; -using MaterialDesignThemes.Wpf; +using Artemis.UI.Shared; using Stylet; namespace Artemis.UI.Screens.Settings.Tabs.Plugins @@ -9,12 +8,14 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins { private readonly PluginConfigurationViewModel _configurationViewModel; - public PluginSettingsWindowViewModel(PluginConfigurationViewModel configurationViewModel, PackIconKind icon) + public PluginSettingsWindowViewModel(PluginConfigurationViewModel configurationViewModel, object icon) { _configurationViewModel = configurationViewModel ?? throw new ArgumentNullException(nameof(configurationViewModel)); Icon = icon; } + public object Icon { get; } + protected override void OnInitialActivate() { ActiveItem = _configurationViewModel; @@ -23,8 +24,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins base.OnInitialActivate(); } - public PackIconKind Icon { get; } - private void ActiveItemOnClosed(object sender, CloseEventArgs e) { ActiveItem.Closed -= ActiveItemOnClosed; diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml b/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml index 9da80b24c..d2d3be984 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml +++ b/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml @@ -25,6 +25,7 @@ VerticalAlignment="Top" Height="120" /> _sidebarItems; private Dictionary _sidebarModules; - public SidebarViewModel(IKernel kernel, IEventAggregator eventAggregator, IModuleVmFactory moduleVmFactory, IPluginService pluginService) + public SidebarViewModel(IKernel kernel, IEventAggregator eventAggregator, IModuleVmFactory moduleVmFactory, IPluginManagementService pluginManagementService) { _kernel = kernel; _moduleVmFactory = moduleVmFactory; - _pluginService = pluginService; + _pluginManagementService = pluginManagementService; SidebarModules = new Dictionary(); SidebarItems = new BindableCollection(); @@ -49,8 +47,8 @@ namespace Artemis.UI.Screens.Sidebar _activeModulesUpdateTimer.Start(); _activeModulesUpdateTimer.Elapsed += ActiveModulesUpdateTimerOnElapsed; - _pluginService.PluginEnabled += PluginServiceOnPluginEnabled; - _pluginService.PluginDisabled += PluginServiceOnPluginDisabled; + _pluginManagementService.PluginFeatureEnabled += OnFeatureEnabled; + _pluginManagementService.PluginFeatureDisabled += OnFeatureDisabled; SetupSidebar(); eventAggregator.Subscribe(this); @@ -91,18 +89,6 @@ namespace Artemis.UI.Screens.Sidebar } } - public void Dispose() - { - SelectedItem?.Deactivate(); - SelectedItem = null; - - _pluginService.PluginEnabled -= PluginServiceOnPluginEnabled; - _pluginService.PluginDisabled -= PluginServiceOnPluginDisabled; - - _activeModulesUpdateTimer.Stop(); - _activeModulesUpdateTimer.Elapsed -= ActiveModulesUpdateTimerOnElapsed; - } - public void SetupSidebar() { SidebarItems.Clear(); @@ -118,7 +104,7 @@ namespace Artemis.UI.Screens.Sidebar // Add all activated modules SidebarItems.Add(new DividerNavigationItem()); SidebarItems.Add(new SubheaderNavigationItem {Subheader = "Modules"}); - List modules = _pluginService.GetPluginsOfType().ToList(); + List modules = _pluginManagementService.GetFeaturesOfType().ToList(); foreach (Module module in modules) AddModule(module); @@ -146,7 +132,7 @@ namespace Artemis.UI.Screens.Sidebar FirstLevelNavigationItem sidebarItem = new FirstLevelNavigationItem { - Icon = PluginUtilities.GetPluginIcon(module.PluginInfo, module.DisplayIcon), + Icon = PluginUtilities.GetPluginIcon(module.Plugin, module.DisplayIcon), Label = module.DisplayName }; SidebarItems.Add(sidebarItem); @@ -208,17 +194,29 @@ namespace Artemis.UI.Screens.Sidebar SelectedItem = SidebarModules.ContainsKey(sidebarItem) ? _moduleVmFactory.CreateModuleRootViewModel(SidebarModules[sidebarItem]) : null; } + public void Dispose() + { + SelectedItem?.Deactivate(); + SelectedItem = null; + + _pluginManagementService.PluginFeatureEnabled -= OnFeatureEnabled; + _pluginManagementService.PluginFeatureDisabled -= OnFeatureDisabled; + + _activeModulesUpdateTimer.Stop(); + _activeModulesUpdateTimer.Elapsed -= ActiveModulesUpdateTimerOnElapsed; + } + #region Event handlers - private void PluginServiceOnPluginEnabled(object sender, PluginEventArgs e) + private void OnFeatureEnabled(object? sender, PluginFeatureEventArgs e) { - if (e.PluginInfo.Instance is Module module) + if (e.PluginFeature is Module module) AddModule(module); } - private void PluginServiceOnPluginDisabled(object sender, PluginEventArgs e) + private void OnFeatureDisabled(object? sender, PluginFeatureEventArgs e) { - if (e.PluginInfo.Instance is Module module) + if (e.PluginFeature is Module module) RemoveModule(module); } diff --git a/src/Artemis.UI/Screens/Splash/SplashViewModel.cs b/src/Artemis.UI/Screens/Splash/SplashViewModel.cs index 33e205a25..dcb747bf6 100644 --- a/src/Artemis.UI/Screens/Splash/SplashViewModel.cs +++ b/src/Artemis.UI/Screens/Splash/SplashViewModel.cs @@ -2,6 +2,7 @@ using System.Windows.Input; using Artemis.Core; using Artemis.Core.Services; +using Humanizer; using MaterialDesignExtensions.Controls; using Stylet; @@ -10,13 +11,13 @@ namespace Artemis.UI.Screens.Splash public class SplashViewModel : Screen { private readonly ICoreService _coreService; - private readonly IPluginService _pluginService; + private readonly IPluginManagementService _pluginManagementService; private string _status; - public SplashViewModel(ICoreService coreService, IPluginService pluginService) + public SplashViewModel(ICoreService coreService, IPluginManagementService pluginManagementService) { _coreService = coreService; - _pluginService = pluginService; + _pluginManagementService = pluginManagementService; Status = "Initializing Core"; } @@ -37,46 +38,60 @@ namespace Artemis.UI.Screens.Splash protected override void OnInitialActivate() { _coreService.Initialized += OnCoreServiceOnInitialized; - _pluginService.CopyingBuildInPlugins += OnPluginServiceOnCopyingBuildInPlugins; - _pluginService.PluginLoading += OnPluginServiceOnPluginLoading; - _pluginService.PluginLoaded += OnPluginServiceOnPluginLoaded; - _pluginService.PluginEnabling += PluginServiceOnPluginEnabling; - _pluginService.PluginEnabled += PluginServiceOnPluginEnabled; + _pluginManagementService.CopyingBuildInPlugins += OnPluginManagementServiceOnCopyingBuildInPluginsManagement; + _pluginManagementService.PluginLoading += OnPluginManagementServiceOnPluginManagementLoading; + _pluginManagementService.PluginLoaded += OnPluginManagementServiceOnPluginManagementLoaded; + _pluginManagementService.PluginEnabling += PluginManagementServiceOnPluginManagementEnabling; + _pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginManagementEnabled; + _pluginManagementService.PluginFeatureEnabling += PluginManagementServiceOnPluginFeatureEnabling; + _pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureEnabled; base.OnInitialActivate(); } protected override void OnClose() { _coreService.Initialized -= OnCoreServiceOnInitialized; - _pluginService.CopyingBuildInPlugins -= OnPluginServiceOnCopyingBuildInPlugins; - _pluginService.PluginLoading -= OnPluginServiceOnPluginLoading; - _pluginService.PluginLoaded -= OnPluginServiceOnPluginLoaded; - _pluginService.PluginEnabling -= PluginServiceOnPluginEnabling; - _pluginService.PluginEnabled -= PluginServiceOnPluginEnabled; + _pluginManagementService.CopyingBuildInPlugins -= OnPluginManagementServiceOnCopyingBuildInPluginsManagement; + _pluginManagementService.PluginLoading -= OnPluginManagementServiceOnPluginManagementLoading; + _pluginManagementService.PluginLoaded -= OnPluginManagementServiceOnPluginManagementLoaded; + _pluginManagementService.PluginEnabling -= PluginManagementServiceOnPluginManagementEnabling; + _pluginManagementService.PluginEnabled -= PluginManagementServiceOnPluginManagementEnabled; + _pluginManagementService.PluginFeatureEnabling -= PluginManagementServiceOnPluginFeatureEnabling; + _pluginManagementService.PluginFeatureEnabled -= PluginManagementServiceOnPluginFeatureEnabled; base.OnClose(); } - private void OnPluginServiceOnPluginLoaded(object sender, PluginEventArgs args) + private void OnPluginManagementServiceOnPluginManagementLoaded(object sender, PluginEventArgs args) { Status = "Initializing UI"; } - private void OnPluginServiceOnPluginLoading(object sender, PluginEventArgs args) + private void OnPluginManagementServiceOnPluginManagementLoading(object sender, PluginEventArgs args) { - Status = "Loading plugin: " + args.PluginInfo.Name; + Status = "Loading plugin: " + args.Plugin.Info.Name; } - private void PluginServiceOnPluginEnabled(object sender, PluginEventArgs args) + private void PluginManagementServiceOnPluginManagementEnabled(object sender, PluginEventArgs args) { Status = "Initializing UI"; } - private void PluginServiceOnPluginEnabling(object sender, PluginEventArgs args) + private void PluginManagementServiceOnPluginManagementEnabling(object sender, PluginEventArgs args) { - Status = "Enabling plugin: " + args.PluginInfo.Name; + Status = "Enabling plugin: " + args.Plugin.Info.Name; } - private void OnPluginServiceOnCopyingBuildInPlugins(object sender, EventArgs args) + private void PluginManagementServiceOnPluginFeatureEnabling(object? sender, PluginFeatureEventArgs e) + { + Status = "Enabling: " + e.PluginFeature.GetType().Name.Humanize(); + } + + private void PluginManagementServiceOnPluginFeatureEnabled(object? sender, PluginFeatureEventArgs e) + { + Status = "Initializing UI"; + } + + private void OnPluginManagementServiceOnCopyingBuildInPluginsManagement(object sender, EventArgs args) { Status = "Updating built-in plugins"; } diff --git a/src/Artemis.UI/Screens/TrayViewModel.cs b/src/Artemis.UI/Screens/TrayViewModel.cs index 76b792bc4..a6c2386d1 100644 --- a/src/Artemis.UI/Screens/TrayViewModel.cs +++ b/src/Artemis.UI/Screens/TrayViewModel.cs @@ -1,5 +1,4 @@ -using Artemis.Core; -using Artemis.Core.Services; +using Artemis.Core.Services; using Artemis.UI.Events; using Artemis.UI.Screens.Splash; using Artemis.UI.Services.Interfaces; diff --git a/src/Artemis.UI/Services/RegistrationService.cs b/src/Artemis.UI/Services/RegistrationService.cs index 1413abbca..abfb32371 100644 --- a/src/Artemis.UI/Services/RegistrationService.cs +++ b/src/Artemis.UI/Services/RegistrationService.cs @@ -1,4 +1,5 @@ -using Artemis.Core; +using System.Linq; +using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.DefaultTypes.DataModel.Display; using Artemis.UI.DefaultTypes.DataModel.Input; @@ -13,19 +14,19 @@ namespace Artemis.UI.Services { private readonly IDataModelUIService _dataModelUIService; private readonly IProfileEditorService _profileEditorService; - private readonly IPluginService _pluginService; + private readonly IPluginManagementService _pluginManagementService; private bool _registeredBuiltInDataModelDisplays; private bool _registeredBuiltInDataModelInputs; private bool _registeredBuiltInPropertyEditors; - public RegistrationService(IDataModelUIService dataModelUIService, IProfileEditorService profileEditorService, IPluginService pluginService) + public RegistrationService(IDataModelUIService dataModelUIService, IProfileEditorService profileEditorService, IPluginManagementService pluginManagementService) { _dataModelUIService = dataModelUIService; _profileEditorService = profileEditorService; - _pluginService = pluginService; + _pluginManagementService = pluginManagementService; LoadPluginModules(); - pluginService.PluginLoaded += PluginServiceOnPluginLoaded; + pluginManagementService.PluginLoaded += PluginServiceOnPluginLoaded; } public void RegisterBuiltInDataModelDisplays() @@ -33,7 +34,7 @@ namespace Artemis.UI.Services if (_registeredBuiltInDataModelDisplays) return; - _dataModelUIService.RegisterDataModelDisplay(Constants.CorePluginInfo); + _dataModelUIService.RegisterDataModelDisplay(Constants.CorePlugin); _registeredBuiltInDataModelDisplays = true; } @@ -43,12 +44,12 @@ namespace Artemis.UI.Services if (_registeredBuiltInDataModelInputs) return; - _dataModelUIService.RegisterDataModelInput(Constants.CorePluginInfo, Constants.FloatNumberTypes); - _dataModelUIService.RegisterDataModelInput(Constants.CorePluginInfo, Constants.IntegralNumberTypes); - _dataModelUIService.RegisterDataModelInput(Constants.CorePluginInfo, null); - _dataModelUIService.RegisterDataModelInput(Constants.CorePluginInfo, null); - _dataModelUIService.RegisterDataModelInput(Constants.CorePluginInfo, null); - _dataModelUIService.RegisterDataModelInput(Constants.CorePluginInfo, null); + _dataModelUIService.RegisterDataModelInput(Constants.CorePlugin, Constants.FloatNumberTypes); + _dataModelUIService.RegisterDataModelInput(Constants.CorePlugin, Constants.IntegralNumberTypes); + _dataModelUIService.RegisterDataModelInput(Constants.CorePlugin, null); + _dataModelUIService.RegisterDataModelInput(Constants.CorePlugin, null); + _dataModelUIService.RegisterDataModelInput(Constants.CorePlugin, null); + _dataModelUIService.RegisterDataModelInput(Constants.CorePlugin, null); _registeredBuiltInDataModelInputs = true; } @@ -58,30 +59,30 @@ namespace Artemis.UI.Services if (_registeredBuiltInPropertyEditors) return; - _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); - _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); - _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); - _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); - _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); - _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); - _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); - _profileEditorService.RegisterPropertyInput(typeof(EnumPropertyInputViewModel<>), Constants.CorePluginInfo); - _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); - _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); - _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); + _profileEditorService.RegisterPropertyInput(Constants.CorePlugin); + _profileEditorService.RegisterPropertyInput(Constants.CorePlugin); + _profileEditorService.RegisterPropertyInput(Constants.CorePlugin); + _profileEditorService.RegisterPropertyInput(Constants.CorePlugin); + _profileEditorService.RegisterPropertyInput(Constants.CorePlugin); + _profileEditorService.RegisterPropertyInput(Constants.CorePlugin); + _profileEditorService.RegisterPropertyInput(Constants.CorePlugin); + _profileEditorService.RegisterPropertyInput(typeof(EnumPropertyInputViewModel<>), Constants.CorePlugin); + _profileEditorService.RegisterPropertyInput(Constants.CorePlugin); + _profileEditorService.RegisterPropertyInput(Constants.CorePlugin); + _profileEditorService.RegisterPropertyInput(Constants.CorePlugin); _registeredBuiltInPropertyEditors = true; } private void PluginServiceOnPluginLoaded(object? sender, PluginEventArgs e) { - e.PluginInfo.Kernel.Load(new[] { new PluginUIModule(e.PluginInfo) }); + e.Plugin.Kernel.Load(new[] {new PluginUIModule(e.Plugin)}); } private void LoadPluginModules() { - foreach (PluginInfo pluginInfo in _pluginService.GetAllPluginInfo()) - pluginInfo.Kernel.Load(new[] { new PluginUIModule(pluginInfo) }); + foreach (Plugin plugin in _pluginManagementService.GetAllPlugins().Where(p => p.IsEnabled)) + plugin.Kernel.Load(new[] {new PluginUIModule(plugin)}); } } diff --git a/src/Artemis.UI/packages.lock.json b/src/Artemis.UI/packages.lock.json index 49487b577..4a4eefa79 100644 --- a/src/Artemis.UI/packages.lock.json +++ b/src/Artemis.UI/packages.lock.json @@ -1363,7 +1363,6 @@ "Serilog.Sinks.Debug": "1.0.1", "Serilog.Sinks.File": "4.1.0", "SkiaSharp": "1.68.3", - "Stylet": "1.3.4", "System.Buffers": "4.5.0", "System.Numerics.Vectors": "4.5.0", "System.Reflection.Metadata": "1.8.0", diff --git a/src/Artemis.sln.DotSettings b/src/Artemis.sln.DotSettings index d11049d68..2df38068d 100644 --- a/src/Artemis.sln.DotSettings +++ b/src/Artemis.sln.DotSettings @@ -222,4 +222,7 @@ True True True + True + + True \ No newline at end of file