diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index f3f636825..7e8af7b5f 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -51,6 +51,9 @@ MinimumRecommendedRules.ruleset + + ..\packages\AppDomainToolkit.1.0.4.3\lib\net\AppDomainToolkit.dll + ..\packages\Castle.Core.4.4.0\lib\net45\Castle.Core.dll diff --git a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs index 50c4b9fbd..8b719727d 100644 --- a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs +++ b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs @@ -18,7 +18,7 @@ namespace Artemis.Core.Plugins.Abstract public abstract bool ExpandsMainDataModel { get; } /// - public void LoadPlugin() + public void EnablePlugin() { // Load and activate the last active profile } diff --git a/src/Artemis.Core/Plugins/Interfaces/IPlugin.cs b/src/Artemis.Core/Plugins/Interfaces/IPlugin.cs index f9ef4fd99..238122061 100644 --- a/src/Artemis.Core/Plugins/Interfaces/IPlugin.cs +++ b/src/Artemis.Core/Plugins/Interfaces/IPlugin.cs @@ -10,6 +10,6 @@ namespace Artemis.Core.Plugins.Interfaces /// /// Called when the plugin is loaded /// - void LoadPlugin(); + void EnablePlugin(); } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Models/PluginInfo.cs b/src/Artemis.Core/Plugins/Models/PluginInfo.cs index c11d66f61..8cba360d8 100644 --- a/src/Artemis.Core/Plugins/Models/PluginInfo.cs +++ b/src/Artemis.Core/Plugins/Models/PluginInfo.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using AppDomainToolkit; using Artemis.Core.Plugins.Interfaces; using Newtonsoft.Json; @@ -7,11 +7,6 @@ namespace Artemis.Core.Plugins.Models { public class PluginInfo { - public PluginInfo() - { - Instances = new List(); - } - /// /// The plugins GUID /// @@ -39,10 +34,21 @@ namespace Artemis.Core.Plugins.Models public string Folder { get; set; } /// - /// A references to the types implementing IPlugin, available after successful load + /// A reference to the type implementing IPlugin, available after successful load /// [JsonIgnore] - public List Instances { get; set; } + public IPlugin Instance { get; set; } + + /// + /// Indicates whether the user enabled the plugin or not + /// + [JsonIgnore] + public bool Enabled { get; set; } + + /// + /// The AppDomain context of this plugin + /// + internal AppDomainContext Context { get; set; } public override string ToString() { diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 874c53452..48ec8119d 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -48,7 +48,7 @@ namespace Artemis.Core.Services { try { - var modules = _pluginService.Plugins.SelectMany(p => p.Instances).OfType().ToList(); + var modules = _pluginService.GetModules(); // Update all active modules foreach (var module in modules) diff --git a/src/Artemis.Core/Services/Interfaces/IPluginService.cs b/src/Artemis.Core/Services/Interfaces/IPluginService.cs index 602f37cc6..85e6bde9d 100644 --- a/src/Artemis.Core/Services/Interfaces/IPluginService.cs +++ b/src/Artemis.Core/Services/Interfaces/IPluginService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Threading.Tasks; using Artemis.Core.Events; @@ -45,5 +46,11 @@ namespace Artemis.Core.Services.Interfaces /// The GUID of the layer type to find /// An instance of the layer type ILayerType GetLayerTypeByGuid(Guid layerTypeGuid); + + /// + /// Returns all the plugins implementing + /// + /// + IReadOnlyList GetModules(); } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/PluginService.cs b/src/Artemis.Core/Services/PluginService.cs index 080a30fb5..d7ecb467d 100644 --- a/src/Artemis.Core/Services/PluginService.cs +++ b/src/Artemis.Core/Services/PluginService.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; -using System.Reflection; using System.Threading.Tasks; +using AppDomainToolkit; using Artemis.Core.Events; using Artemis.Core.Exceptions; using Artemis.Core.Plugins.Exceptions; @@ -53,7 +53,6 @@ namespace Artemis.Core.Services // Load the plugin assemblies into the plugin context var directory = new DirectoryInfo(Path.Combine(Constants.DataFolder, "plugins")); foreach (var subDirectory in directory.EnumerateDirectories()) - { try { // Load the metadata @@ -63,42 +62,58 @@ namespace Artemis.Core.Services // Locate the main entry var pluginInfo = JsonConvert.DeserializeObject(File.ReadAllText(metadataFile)); + // TODO Just temporarily until settings are in place + pluginInfo.Enabled = true; var mainFile = Path.Combine(subDirectory.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 IPlugin and register them with DI - Assembly assembly; + var setupInfo = new AppDomainSetup + { + ApplicationName = pluginInfo.Guid.ToString(), + ApplicationBase = AppDomain.CurrentDomain.BaseDirectory, + PrivateBinPath = subDirectory.FullName + }; + pluginInfo.Context = AppDomainContext.Create(setupInfo); + try { - assembly = Assembly.LoadFile(mainFile); + pluginInfo.Context.LoadAssemblyWithReferences(LoadMethod.LoadFrom, mainFile); } catch (Exception e) { throw new ArtemisPluginException(pluginInfo, "Failed to load the plugins assembly", e); } - var pluginTypes = assembly.GetTypes().Where(t => typeof(IPlugin).IsAssignableFrom(t)).ToArray(); - foreach (var pluginType in pluginTypes) - { - _childKernel.Bind().To(pluginType).InSingletonScope(); - try - { - pluginInfo.Instances.Add((IPlugin) _childKernel.Get(pluginType)); - } - catch (Exception e) - { - throw new ArtemisPluginException(pluginInfo, "Failed to instantiate the plugin", e); - } - } + // Get the IPlugin implementation from the main assembly and if there is only one, instantiate it + var mainAssembly = pluginInfo.Context.Domain.GetAssemblies().First(a => a.Location == mainFile); + var pluginTypes = mainAssembly.GetTypes().Where(t => typeof(IPlugin).IsAssignableFrom(t)).ToList(); + if (pluginTypes.Count > 1) + throw new ArtemisPluginException(pluginInfo, $"Plugin contains {pluginTypes.Count} implementations of IPlugin, only 1 allowed"); + if (pluginTypes.Count == 0) + throw new ArtemisPluginException(pluginInfo, "Plugin contains no implementation of IPlugin"); + var pluginType = pluginTypes.Single(); + _childKernel.Bind().To(pluginType).InSingletonScope(); + try + { + pluginInfo.Instance = (IPlugin) _childKernel.Get(pluginType); + } + catch (Exception e) + { + throw new ArtemisPluginException(pluginInfo, "Failed to instantiate the plugin", e); + } _plugins.Add(pluginInfo); } catch (Exception e) { throw new ArtemisPluginException("Failed to load plugin", e); } - } + + // Activate plugins after they are all loaded + foreach (var pluginInfo in _plugins.Where(p => p.Enabled)) + pluginInfo.Instance.EnablePlugin(); }); OnFinishedLoadedPlugins(); @@ -111,11 +126,15 @@ namespace Artemis.Core.Services if (pluginInfo == null) return null; - var layerType = pluginInfo.Instances.SingleOrDefault(p => p is ILayerType); - if (layerType == null) + if (!(pluginInfo.Instance is ILayerType layerType)) throw new ArtemisPluginException(pluginInfo, "Plugin is expected to implement exactly one ILayerType"); - return (ILayerType) layerType; + return layerType; + } + + public IReadOnlyList GetModules() + { + return Plugins.Where(p => p.Instance is IModule).Select(p => (IModule) p.Instance).ToList(); } public void Dispose() diff --git a/src/Artemis.Core/app.config b/src/Artemis.Core/app.config index ec91ee1d6..58347c373 100644 --- a/src/Artemis.Core/app.config +++ b/src/Artemis.Core/app.config @@ -28,8 +28,7 @@ - + @@ -53,8 +52,7 @@ - + @@ -70,8 +68,7 @@ - + @@ -87,8 +84,7 @@ - + @@ -96,8 +92,7 @@ - + diff --git a/src/Artemis.Core/packages.config b/src/Artemis.Core/packages.config index 411ff3c70..ce487c687 100644 --- a/src/Artemis.Core/packages.config +++ b/src/Artemis.Core/packages.config @@ -1,6 +1,6 @@  - + diff --git a/src/Artemis.Plugins.LayerTypes.Brush/Artemis.Plugins.LayerTypes.Brush.csproj b/src/Artemis.Plugins.LayerTypes.Brush/Artemis.Plugins.LayerTypes.Brush.csproj index d2008d288..d75cc3ea1 100644 --- a/src/Artemis.Plugins.LayerTypes.Brush/Artemis.Plugins.LayerTypes.Brush.csproj +++ b/src/Artemis.Plugins.LayerTypes.Brush/Artemis.Plugins.LayerTypes.Brush.csproj @@ -33,17 +33,23 @@ + + ..\packages\QRCoder.1.2.5\lib\net40\QRCoder.dll + ..\packages\RGB.NET.Core.0.1.22\lib\net45\RGB.NET.Core.dll + False ..\packages\Stylet.1.1.22\lib\net45\Stylet.dll + False ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll + False @@ -52,6 +58,9 @@ + + ..\packages\QRCoder.1.2.5\lib\net40\UnityEngine.dll + @@ -63,6 +72,7 @@ {9b811f9b-86b9-4771-87af-72bae7078a36} Artemis.Core + False diff --git a/src/Artemis.Plugins.LayerTypes.Brush/BrushLayerType.cs b/src/Artemis.Plugins.LayerTypes.Brush/BrushLayerType.cs index 10a6bbc6a..67aa2b011 100644 --- a/src/Artemis.Plugins.LayerTypes.Brush/BrushLayerType.cs +++ b/src/Artemis.Plugins.LayerTypes.Brush/BrushLayerType.cs @@ -1,7 +1,7 @@ -using System; -using System.Drawing; +using System.Drawing; using Artemis.Core.Plugins.Interfaces; using Artemis.Core.ProfileElements; +using QRCoder; using RGB.NET.Core; namespace Artemis.Plugins.LayerTypes.Brush @@ -10,12 +10,11 @@ namespace Artemis.Plugins.LayerTypes.Brush { public void Dispose() { - throw new NotImplementedException(); } - public void LoadPlugin() + public void EnablePlugin() { - throw new NotImplementedException(); + var qrGenerator = new QRCodeGenerator(); } public void Update(Layer layer) diff --git a/src/Artemis.Plugins.LayerTypes.Brush/packages.config b/src/Artemis.Plugins.LayerTypes.Brush/packages.config index ac354a2d0..f559146e0 100644 --- a/src/Artemis.Plugins.LayerTypes.Brush/packages.config +++ b/src/Artemis.Plugins.LayerTypes.Brush/packages.config @@ -1,6 +1,7 @@  - + + \ No newline at end of file diff --git a/src/Artemis.Plugins.Modules.General/Artemis.Plugins.Modules.General.csproj b/src/Artemis.Plugins.Modules.General/Artemis.Plugins.Modules.General.csproj index 3ab93ac6f..947614257 100644 --- a/src/Artemis.Plugins.Modules.General/Artemis.Plugins.Modules.General.csproj +++ b/src/Artemis.Plugins.Modules.General/Artemis.Plugins.Modules.General.csproj @@ -33,17 +33,27 @@ + + ..\packages\QRCoder.1.3.5\lib\net40\QRCoder.dll + ..\packages\RGB.NET.Core.0.1.22\lib\net45\RGB.NET.Core.dll + False - - ..\packages\Stylet.1.1.22\lib\net45\Stylet.dll + + ..\packages\Stylet.1.1.17\lib\net45\Stylet.dll + False + + ..\packages\System.Drawing.Common.4.5.0\lib\net461\System.Drawing.Common.dll + False + ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll + False @@ -69,6 +79,7 @@ {9b811f9b-86b9-4771-87af-72bae7078a36} Artemis.Core + False diff --git a/src/Artemis.Plugins.Modules.General/GeneralDataModel.cs b/src/Artemis.Plugins.Modules.General/GeneralDataModel.cs index f00c82340..14e360b3c 100644 --- a/src/Artemis.Plugins.Modules.General/GeneralDataModel.cs +++ b/src/Artemis.Plugins.Modules.General/GeneralDataModel.cs @@ -6,11 +6,11 @@ namespace Artemis.Plugins.Modules.General { public class GeneralDataModel : ModuleDataModel { - [DataModelProperty(DisplayName = "Unique boolean")] - public bool PropertyUniqueToThisDm { get; set; } - public GeneralDataModel(IModule module) : base(module) { } + + [DataModelProperty(DisplayName = "Unique boolean")] + public bool PropertyUniqueToThisDm { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.Plugins.Modules.General/GeneralModule.cs b/src/Artemis.Plugins.Modules.General/GeneralModule.cs index a5089b49c..cafc22e97 100644 --- a/src/Artemis.Plugins.Modules.General/GeneralModule.cs +++ b/src/Artemis.Plugins.Modules.General/GeneralModule.cs @@ -4,6 +4,7 @@ using Artemis.Core; using Artemis.Core.Plugins.Interfaces; using Artemis.Core.Services.Interfaces; using Artemis.Plugins.Modules.General.ViewModels; +using QRCoder; using RGB.NET.Core; using Stylet; using Color = System.Drawing.Color; @@ -63,8 +64,9 @@ namespace Artemis.Plugins.Modules.General _colors = null; } - public void LoadPlugin() + public void EnablePlugin() { + var qrGenerator = new QRCodeGenerator(); PopulateColors(); } diff --git a/src/Artemis.Plugins.Modules.General/packages.config b/src/Artemis.Plugins.Modules.General/packages.config index 3bd1a62d7..a2474caba 100644 --- a/src/Artemis.Plugins.Modules.General/packages.config +++ b/src/Artemis.Plugins.Modules.General/packages.config @@ -1,7 +1,8 @@  - + - + + \ No newline at end of file diff --git a/src/Artemis.UI/App.config b/src/Artemis.UI/App.config index fcbfc6adb..39dcaca51 100644 --- a/src/Artemis.UI/App.config +++ b/src/Artemis.UI/App.config @@ -31,8 +31,7 @@ - + @@ -56,8 +55,7 @@ - + @@ -73,8 +71,7 @@ - + @@ -90,8 +87,7 @@ - + @@ -99,8 +95,7 @@ - + diff --git a/src/Artemis.UI/ViewModels/RootViewModel.cs b/src/Artemis.UI/ViewModels/RootViewModel.cs index 4b6376768..a62f43220 100644 --- a/src/Artemis.UI/ViewModels/RootViewModel.cs +++ b/src/Artemis.UI/ViewModels/RootViewModel.cs @@ -34,7 +34,7 @@ namespace Artemis.UI.ViewModels _pluginService.FinishedLoadedPlugins += PluginServiceOnFinishedLoadedPlugins; if (!LoadingPlugins) - Modules.AddRange(_pluginService.Plugins.SelectMany(p => p.Instances.Where(i => i is IModule).Cast())); + Modules.AddRange(_pluginService.GetModules()); PropertyChanged += OnSelectedModuleChanged; PropertyChanged += OnSelectedPageChanged; @@ -58,7 +58,7 @@ namespace Artemis.UI.ViewModels private void PluginServiceOnFinishedLoadedPlugins(object sender, EventArgs eventArgs) { - Modules.AddRange(_pluginService.Plugins.SelectMany(p => p.Instances.Where(i => i is IModule).Cast())); + Modules.AddRange(_pluginService.GetModules()); SelectedModule = null; LoadingPlugins = false;