Plugins - Separated plugins and implementations as different principles

This commit is contained in:
SpoinkyNL committed 2020-11-09 20:31:54 +01:00
1 parent c0d0e421c7
commit 1e0dc1894d
53 files changed
+438 -410

No files matched your search

@@ -14,10 +14,10 @@ namespace Artemis.Core.DataModelExpansions
private readonly Dictionary<string, DataModel> _dynamicDataModels = new Dictionary<string, DataModel>();
/// <summary>
/// Gets the plugin info this data model belongs to
/// Gets the plugin implementation this data model belongs to
/// </summary>
[DataModelIgnore]
public PluginInfo PluginInfo { get; internal set; }
public DataModelPluginImplementation Implementation { get; internal set; }
/// <summary>
/// Gets the <see cref="DataModelPropertyAttribute" /> describing this data model
@@ -43,9 +43,9 @@ namespace Artemis.Core.DataModelExpansions
/// <returns></returns>
public ReadOnlyCollection<PropertyInfo> GetHiddenProperties()
{
if (PluginInfo.Instance is ProfileModule profileModule)
if (Implementation is ProfileModule profileModule)
return profileModule.HiddenProperties;
if (PluginInfo.Instance is BaseDataModelExpansion dataModelExpansion)
if (Implementation is BaseDataModelExpansion dataModelExpansion)
return dataModelExpansion.HiddenProperties;
return new List<PropertyInfo>().AsReadOnly();
@@ -81,7 +81,7 @@ namespace Artemis.Core.DataModelExpansions
throw new ArtemisCoreException($"Cannot add a dynamic data model with key '{key}' " +
"because the key is already in use by a static property on this data model.");
dynamicDataModel.PluginInfo = PluginInfo;
dynamicDataModel.Implementation = Implementation;
dynamicDataModel.DataModelDescription = new DataModelPropertyAttribute
{
Name = string.IsNullOrWhiteSpace(name) ? key.Humanize() : name,
@@ -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<T>();
DataModel.PluginInfo = PluginInfo;
DataModel.Implementation = PluginInfo;
DataModel.DataModelDescription = GetDataModelDescription();
base.InternalEnablePlugin();
base.InternalEnable();
}
internal override void InternalDisablePlugin()
internal override void InternalDisable()
{
DataModel = null;
base.InternalDisablePlugin();
base.InternalDisable();
}
}
}
@@ -8,7 +8,7 @@ namespace Artemis.Core.DataModelExpansions
/// For internal use only, to implement your own layer property type, extend <see cref="DataModelExpansion{T}" />
/// instead.
/// </summary>
public abstract class BaseDataModelExpansion : Plugin
public abstract class BaseDataModelExpansion : DataModelPluginImplementation
{
/// <summary>
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
@@ -0,0 +1,47 @@
using System;
using System.Threading.Tasks;
namespace Artemis.Core
{
/// <summary>
/// Represents an implementation of a certain type provided by a plugin with support for data models
/// </summary>
public abstract class DataModelPluginImplementation : PluginImplementation
{
/// <summary>
/// Registers a timed update that whenever the plugin is enabled calls the provided <paramref name="action" /> at the
/// provided
/// <paramref name="interval" />
/// </summary>
/// <param name="interval">The interval at which the update should occur</param>
/// <param name="action">
/// The action to call every time the interval has passed. The delta time parameter represents the
/// time passed since the last update in seconds
/// </param>
/// <returns>The resulting plugin update registration which can be used to stop the update</returns>
public TimedUpdateRegistration AddTimedUpdate(TimeSpan interval, Action<double> action)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
return new TimedUpdateRegistration(PluginInfo, interval, action);
}
/// <summary>
/// Registers a timed update that whenever the plugin is enabled calls the provided <paramref name="action" /> at the
/// provided
/// <paramref name="interval" />
/// </summary>
/// <param name="interval">The interval at which the update should occur</param>
/// <param name="asyncAction">
/// 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
/// </param>
/// <returns>The resulting plugin update registration</returns>
public TimedUpdateRegistration AddTimedUpdate(TimeSpan interval, Func<double, Task> asyncAction)
{
if (asyncAction == null)
throw new ArgumentNullException(nameof(asyncAction));
return new TimedUpdateRegistration(PluginInfo, interval, asyncAction);
}
}
}
@@ -10,7 +10,7 @@ namespace Artemis.Core.DeviceProviders
/// <summary>
/// Allows you to implement and register your own device provider
/// </summary>
public abstract class DeviceProvider : Plugin
public abstract class DeviceProvider : PluginImplementation
{
/// <summary>
/// Creates a new instance of the <see cref="DeviceProvider" /> class
@@ -34,7 +34,7 @@ namespace Artemis.Core.DeviceProviders
public ILogger Logger { get; set; }
/// <inheritdoc />
public override void DisablePlugin()
public override void Disable()
{
// Does not happen with device providers, they require Artemis to restart
}
@@ -7,7 +7,7 @@ namespace Artemis.Core.LayerBrushes
/// <summary>
/// Allows you to create one or more <see cref="LayerBrush{T}" />s usable by profile layers.
/// </summary>
public abstract class LayerBrushProvider : Plugin
public abstract class LayerBrushProvider : PluginImplementation
{
private readonly List<LayerBrushDescriptor> _layerBrushDescriptors;
@@ -17,7 +17,7 @@ namespace Artemis.Core.LayerBrushes
protected LayerBrushProvider()
{
_layerBrushDescriptors = new List<LayerBrushDescriptor>();
PluginDisabled += OnPluginDisabled;
Disabled += OnDisabled;
}
/// <summary>
@@ -38,7 +38,7 @@ namespace Artemis.Core.LayerBrushes
/// </param>
protected void RegisterLayerBrushDescriptor<T>(string displayName, string description, string icon) where T : BaseLayerBrush
{
if (!Enabled)
if (!IsEnabled)
throw new ArtemisPluginException(PluginInfo, "Can only add a layer brush descriptor when the plugin is enabled");
LayerBrushDescriptor descriptor = new LayerBrushDescriptor(displayName, description, icon, typeof(T), this);
@@ -46,7 +46,7 @@ namespace Artemis.Core.LayerBrushes
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();
@@ -7,7 +7,7 @@ namespace Artemis.Core.LayerEffects
/// <summary>
/// Allows you to register one or more <see cref="LayerEffect{T}" />s usable by profile layers.
/// </summary>
public abstract class LayerEffectProvider : Plugin
public abstract class LayerEffectProvider : PluginImplementation
{
private readonly List<LayerEffectDescriptor> _layerEffectDescriptors;
@@ -17,7 +17,7 @@ namespace Artemis.Core.LayerEffects
protected LayerEffectProvider()
{
_layerEffectDescriptors = new List<LayerEffectDescriptor>();
PluginDisabled += OnPluginDisabled;
Disabled += OnDisabled;
}
/// <summary>
@@ -38,7 +38,7 @@ namespace Artemis.Core.LayerEffects
/// </param>
protected void RegisterLayerEffectDescriptor<T>(string displayName, string description, string icon) where T : BaseLayerEffect
{
if (!Enabled)
if (!IsEnabled)
throw new ArtemisPluginException(PluginInfo, "Can only add a layer effect descriptor when the plugin is enabled");
LayerEffectDescriptor descriptor = new LayerEffectDescriptor(displayName, description, icon, typeof(T), this);
@@ -46,7 +46,7 @@ namespace Artemis.Core.LayerEffects
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();
+6 -6
View File
@@ -45,18 +45,18 @@ namespace Artemis.Core.Modules
return new DataModelPropertyAttribute {Name = PluginInfo.Name, Description = PluginInfo.Description};
}
internal override void InternalEnablePlugin()
internal override void InternalEnable()
{
DataModel = Activator.CreateInstance<T>();
DataModel.PluginInfo = PluginInfo;
DataModel.Implementation = PluginInfo;
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
/// <summary>
/// Allows you to add support for new games/applications
/// </summary>
public abstract class Module : Plugin
public abstract class Module : DataModelPluginImplementation
{
/// <summary>
/// The modules display name that's shown in the menu
@@ -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<T>();
DataModel.PluginInfo = PluginInfo;
DataModel.Implementation = PluginInfo;
DataModel.DataModelDescription = GetDataModelDescription();
base.InternalEnablePlugin();
base.InternalEnable();
}
internal override void InternalDisablePlugin()
internal override void InternalDisable()
{
Deactivate(true);
base.InternalDisablePlugin();
base.InternalDisable();
DataModel = null;
}
}
+4 -164
View File
@@ -1,178 +1,18 @@
using System;
using System.Threading.Tasks;
namespace Artemis.Core
namespace Artemis.Core
{
/// <summary>
/// This is the base plugin type, use the other interfaces such as Module to create plugins
/// Represents a plugin
/// </summary>
public abstract class Plugin : IDisposable
public abstract class Plugin
{
/// <summary>
/// Gets the plugin info related to this plugin
/// </summary>
public PluginInfo PluginInfo { get; internal set; }
/// <summary>
/// Gets whether the plugin is enabled
/// </summary>
public bool Enabled { get; internal set; }
public PluginInfo Info { get; internal set; }
/// <summary>
/// Gets or sets a configuration dialog for this plugin that is accessible in the UI under Settings > Plugins
/// </summary>
public PluginConfigurationDialog ConfigurationDialog { get; protected set; }
/// <summary>
/// Called when the plugin is activated
/// </summary>
public abstract void EnablePlugin();
/// <summary>
/// Called when the plugin is deactivated or when Artemis shuts down
/// </summary>
public abstract void DisablePlugin();
/// <summary>
/// Registers a timed update that whenever the plugin is enabled calls the provided <paramref name="action" /> at the
/// provided
/// <paramref name="interval" />
/// </summary>
/// <param name="interval">The interval at which the update should occur</param>
/// <param name="action">
/// The action to call every time the interval has passed. The delta time parameter represents the
/// time passed since the last update in seconds
/// </param>
/// <returns>The resulting plugin update registration which can be used to stop the update</returns>
public PluginUpdateRegistration AddTimedUpdate(TimeSpan interval, Action<double> action)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
return new PluginUpdateRegistration(PluginInfo, interval, action);
}
/// <summary>
/// Registers a timed update that whenever the plugin is enabled calls the provided <paramref name="action" /> at the
/// provided
/// <paramref name="interval" />
/// </summary>
/// <param name="interval">The interval at which the update should occur</param>
/// <param name="asyncAction">
/// 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
/// </param>
/// <returns>The resulting plugin update registration</returns>
public PluginUpdateRegistration AddTimedUpdate(TimeSpan interval, Func<double, Task> asyncAction)
{
if (asyncAction == null)
throw new ArgumentNullException(nameof(asyncAction));
return new PluginUpdateRegistration(PluginInfo, interval, asyncAction);
}
internal void SetEnabled(bool enable, bool isAutoEnable = false)
{
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();
}
/// <inheritdoc />
public void Dispose()
{
DisablePlugin();
}
#region Events
/// <summary>
/// Occurs when the plugin is enabled
/// </summary>
public event EventHandler PluginEnabled;
/// <summary>
/// Occurs when the plugin is disabled
/// </summary>
public event EventHandler PluginDisabled;
/// <summary>
/// Triggers the PluginEnabled event
/// </summary>
protected virtual void OnPluginEnabled()
{
PluginEnabled?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Triggers the PluginDisabled event
/// </summary>
protected virtual void OnPluginDisabled()
{
PluginDisabled?.Invoke(this, EventArgs.Empty);
}
#endregion
}
}
@@ -17,7 +17,7 @@ namespace Artemis.Core
/// <summary>
/// The layer brush this dialog belongs to
/// </summary>
internal Plugin Plugin { get; set; }
internal PluginImplementation PluginImplementation { get; set; }
/// <summary>
/// The type of view model the tab contains
@@ -10,25 +10,25 @@ namespace Artemis.Core
/// <summary>
/// Creates a new instance of the <see cref="PluginConfigurationViewModel" /> class
/// </summary>
/// <param name="plugin"></param>
protected PluginConfigurationViewModel(Plugin plugin)
/// <param name="pluginImplementation"></param>
protected PluginConfigurationViewModel(PluginImplementation pluginImplementation)
{
Plugin = plugin;
PluginImplementation = pluginImplementation;
}
/// <summary>
/// Creates a new instance of the <see cref="PluginConfigurationViewModel" /> class with a validator
/// </summary>
/// <param name="plugin"></param>
/// <param name="pluginImplementation"></param>
/// <param name="validator"></param>
protected PluginConfigurationViewModel(Plugin plugin, IModelValidator validator) : base(validator)
protected PluginConfigurationViewModel(PluginImplementation pluginImplementation, IModelValidator validator) : base(validator)
{
Plugin = plugin;
PluginImplementation = pluginImplementation;
}
/// <summary>
/// Gets the plugin this configuration view model is associated with
/// </summary>
public Plugin Plugin { get; }
public PluginImplementation PluginImplementation { get; }
}
}
@@ -0,0 +1,142 @@
using System;
using System.Threading.Tasks;
namespace Artemis.Core
{
/// <summary>
/// Represents an implementation of a certain type provided by a plugin
/// </summary>
public abstract class PluginImplementation : IDisposable
{
/// <summary>
/// Gets the plugin that provides this implementation
/// </summary>
public Plugin Plugin { get; internal set; }
/// <summary>
/// Gets the plugin info related to this plugin
/// </summary>
public PluginInfo PluginInfo { get; internal set; }
/// <summary>
/// Gets whether the plugin is enabled
/// </summary>
public bool IsEnabled { get; internal set; }
/// <summary>
/// Called when the implementation is activated
/// </summary>
public abstract void Enable();
/// <summary>
/// Called when the implementation is deactivated or when Artemis shuts down
/// </summary>
public abstract void Disable();
internal void SetEnabled(bool enable, bool isAutoEnable = false)
{
if (enable && !IsEnabled)
{
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);
}
IsEnabled = true;
PluginInfo.IsEnabled = 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(InternalEnable);
if (!enableTask.Wait(TimeSpan.FromSeconds(15)))
throw new ArtemisPluginException(PluginInfo, "Plugin load timeout");
PluginInfo.LoadException = null;
OnEnabled();
}
// If enable failed, put it back in a disabled state
catch (Exception e)
{
IsEnabled = false;
PluginInfo.IsEnabled = false;
PluginInfo.LoadException = e;
throw;
}
finally
{
if (!(PluginInfo.LoadException is ArtemisPluginLockException))
PluginInfo.DeleteLockFile();
}
}
else if (!enable && IsEnabled)
{
IsEnabled = false;
PluginInfo.IsEnabled = false;
// Even if disable failed, still leave it in a disabled state to avoid more issues
InternalDisable();
OnDisabled();
}
// 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 && !IsEnabled)
{
PluginInfo.IsEnabled = false;
}
}
internal virtual void InternalEnable()
{
Enable();
}
internal virtual void InternalDisable()
{
Disable();
}
/// <inheritdoc />
public void Dispose()
{
Disable();
}
#region Events
/// <summary>
/// Occurs when the implementation is enabled
/// </summary>
public event EventHandler? Enabled;
/// <summary>
/// Occurs when the implementation is disabled
/// </summary>
public event EventHandler? Disabled;
/// <summary>
/// Triggers the PluginEnabled event
/// </summary>
protected virtual void OnEnabled()
{
Enabled?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Triggers the PluginDisabled event
/// </summary>
protected virtual void OnDisabled()
{
Disabled?.Invoke(this, EventArgs.Empty);
}
#endregion
}
}
+18 -19
View File
@@ -5,7 +5,6 @@ using Artemis.Storage.Entities.Plugins;
using McMaster.NETCore.Plugins;
using Newtonsoft.Json;
using Ninject;
using Ninject.Extensions.ChildKernel;
using Stylet;
namespace Artemis.Core
@@ -18,10 +17,10 @@ namespace Artemis.Core
{
private string _description;
private DirectoryInfo _directory;
private bool _enabled;
private Guid _guid;
private string _icon;
private Plugin _instance;
private bool _isEnabled;
private Exception _loadException;
private string _main;
private string _name;
@@ -103,7 +102,7 @@ namespace Artemis.Core
}
/// <summary>
/// A reference to the type implementing Plugin, available after successful load
/// Gets the plugin this info is associated with
/// </summary>
public Plugin Instance
{
@@ -114,10 +113,10 @@ namespace Artemis.Core
/// <summary>
/// Indicates whether the user enabled the plugin or not
/// </summary>
public bool Enabled
public bool IsEnabled
{
get => _enabled;
internal set => SetAndNotify(ref _enabled, value);
get => _isEnabled;
internal set => SetAndNotify(ref _isEnabled, value);
}
/// <summary>
@@ -129,36 +128,41 @@ namespace Artemis.Core
internal set => SetAndNotify(ref _loadException, value);
}
/// <summary>
/// The PluginLoader backing this plugin
/// </summary>
internal PluginLoader PluginLoader { get; set; }
/// <summary>
/// The assembly the plugin code lives in
/// </summary>
public Assembly Assembly { get; internal set; }
/// <summary>
/// The Ninject kernel of the plugin
/// The Ninject kernel of the plugin
/// </summary>
public IKernel Kernel { get; internal set; }
/// <summary>
/// The PluginLoader backing this plugin
/// </summary>
internal PluginLoader PluginLoader { get; set; }
/// <summary>
/// The entity representing the plugin
/// </summary>
internal PluginEntity PluginEntity { get; set; }
/// <inheritdoc />
public override string ToString()
{
return $"{Name} v{Version} - {Guid}";
}
public string? ResolveRelativePath(string path)
{
return path == null ? null : Path.Combine(Directory.FullName, path);
}
internal void ApplyToEntity()
{
PluginEntity.Id = Guid;
PluginEntity.IsEnabled = Enabled;
PluginEntity.IsEnabled = IsEnabled;
}
internal void CreateLockFile()
@@ -176,10 +180,5 @@ namespace Artemis.Core
{
return File.Exists(Path.Combine(Directory.FullName, "artemis.lock"));
}
public string? ResolveRelativePath(string path)
{
return path == null ? null : Path.Combine(Directory.FullName, path);
}
}
}
@@ -8,32 +8,32 @@ namespace Artemis.Core
/// <summary>
/// Represents a registration for a timed plugin update
/// </summary>
public class PluginUpdateRegistration
public class TimedUpdateRegistration
{
private DateTime _lastEvent;
private Timer _timer;
internal PluginUpdateRegistration(PluginInfo pluginInfo, TimeSpan interval, Action<double> action)
internal TimedUpdateRegistration(PluginInfo pluginInfo, TimeSpan interval, Action<double> action)
{
PluginInfo = pluginInfo;
Interval = interval;
Action = action;
PluginInfo.Instance.PluginEnabled += InstanceOnPluginEnabled;
PluginInfo.Instance.PluginDisabled += InstanceOnPluginDisabled;
if (PluginInfo.Instance.Enabled)
PluginInfo.Instance.Enabled += InstanceOnEnabled;
PluginInfo.Instance.Disabled += InstanceOnDisabled;
if (PluginInfo.Instance.IsEnabled)
Start();
}
internal PluginUpdateRegistration(PluginInfo pluginInfo, TimeSpan interval, Func<double, Task> asyncAction)
internal TimedUpdateRegistration(PluginInfo pluginInfo, TimeSpan interval, Func<double, Task> asyncAction)
{
PluginInfo = pluginInfo;
Interval = interval;
AsyncAction = asyncAction;
PluginInfo.Instance.PluginEnabled += InstanceOnPluginEnabled;
PluginInfo.Instance.PluginDisabled += InstanceOnPluginDisabled;
if (PluginInfo.Instance.Enabled)
PluginInfo.Instance.Enabled += InstanceOnEnabled;
PluginInfo.Instance.Disabled += InstanceOnDisabled;
if (PluginInfo.Instance.IsEnabled)
Start();
}
@@ -66,7 +66,7 @@ namespace Artemis.Core
{
lock (this)
{
if (!PluginInfo.Instance.Enabled)
if (!PluginInfo.Instance.IsEnabled)
throw new ArtemisPluginException("Cannot start a timed update for a disabled plugin");
if (_timer != null)
@@ -99,7 +99,7 @@ namespace Artemis.Core
private void TimerOnElapsed(object sender, ElapsedEventArgs e)
{
if (!PluginInfo.Instance.Enabled)
if (!PluginInfo.Instance.IsEnabled)
return;
lock (this)
@@ -121,12 +121,12 @@ namespace Artemis.Core
}
}
private void InstanceOnPluginEnabled(object sender, EventArgs e)
private void InstanceOnEnabled(object sender, EventArgs e)
{
Start();
}
private void InstanceOnPluginDisabled(object sender, EventArgs e)
private void InstanceOnDisabled(object sender, EventArgs e)
{
Stop();
}