diff --git a/src/neo/Consensus/ConsensusService.cs b/src/neo/Consensus/ConsensusService.cs index 57151481d7..5ead54d233 100644 --- a/src/neo/Consensus/ConsensusService.cs +++ b/src/neo/Consensus/ConsensusService.cs @@ -189,7 +189,7 @@ private void InitializeConsensus(byte viewNumber) private void Log(string message, LogLevel level = LogLevel.Info) { - Plugin.Log(nameof(ConsensusService), level, message); + Utility.Log(nameof(ConsensusService), level, message); } private void OnChangeViewReceived(ConsensusPayload payload, ChangeView message) diff --git a/src/neo/Plugins/LogLevel.cs b/src/neo/LogLevel.cs similarity index 84% rename from src/neo/Plugins/LogLevel.cs rename to src/neo/LogLevel.cs index 8f6e0b9df7..1aeef70a10 100644 --- a/src/neo/Plugins/LogLevel.cs +++ b/src/neo/LogLevel.cs @@ -1,4 +1,4 @@ -namespace Neo.Plugins +namespace Neo { public enum LogLevel : byte { diff --git a/src/neo/NeoSystem.cs b/src/neo/NeoSystem.cs index c5e0d0d8e5..e9e8335bb4 100644 --- a/src/neo/NeoSystem.cs +++ b/src/neo/NeoSystem.cs @@ -39,7 +39,8 @@ public NeoSystem(string storageEngine = null) this.Blockchain = ActorSystem.ActorOf(Ledger.Blockchain.Props(this, store)); this.LocalNode = ActorSystem.ActorOf(Network.P2P.LocalNode.Props(this)); this.TaskManager = ActorSystem.ActorOf(Network.P2P.TaskManager.Props(this)); - Plugin.NotifyPluginsLoadedAfterSystemConstructed(); + foreach (var plugin in Plugin.Plugins) + plugin.OnPluginsLoaded(); } public void Dispose() diff --git a/src/neo/Plugins/IP2PPlugin.cs b/src/neo/Plugins/IP2PPlugin.cs index e2043104fc..40c083545b 100644 --- a/src/neo/Plugins/IP2PPlugin.cs +++ b/src/neo/Plugins/IP2PPlugin.cs @@ -5,7 +5,7 @@ namespace Neo.Plugins { public interface IP2PPlugin { - bool OnP2PMessage(Message message); - bool OnConsensusMessage(ConsensusPayload payload); + bool OnP2PMessage(Message message) => true; + bool OnConsensusMessage(ConsensusPayload payload) => true; } } diff --git a/src/neo/Plugins/IPersistencePlugin.cs b/src/neo/Plugins/IPersistencePlugin.cs index 14a3316115..2884607b39 100644 --- a/src/neo/Plugins/IPersistencePlugin.cs +++ b/src/neo/Plugins/IPersistencePlugin.cs @@ -7,8 +7,8 @@ namespace Neo.Plugins { public interface IPersistencePlugin { - void OnPersist(StoreView snapshot, IReadOnlyList applicationExecutedList); - void OnCommit(StoreView snapshot); - bool ShouldThrowExceptionFromCommit(Exception ex); + void OnPersist(StoreView snapshot, IReadOnlyList applicationExecutedList) { } + void OnCommit(StoreView snapshot) { } + bool ShouldThrowExceptionFromCommit(Exception ex) => false; } } diff --git a/src/neo/Plugins/Plugin.cs b/src/neo/Plugins/Plugin.cs index caacc17d85..918a1e4ae3 100644 --- a/src/neo/Plugins/Plugin.cs +++ b/src/neo/Plugins/Plugin.cs @@ -5,34 +5,35 @@ using System.Linq; using System.Reflection; using System.Threading; +using static System.IO.Path; namespace Neo.Plugins { public abstract class Plugin : IDisposable { public static readonly List Plugins = new List(); - private static readonly List Loggers = new List(); + internal static readonly List Loggers = new List(); internal static readonly Dictionary Storages = new Dictionary(); internal static readonly List RpcPlugins = new List(); internal static readonly List PersistencePlugins = new List(); internal static readonly List P2PPlugins = new List(); internal static readonly List TxObserverPlugins = new List(); - private static readonly string pluginsPath = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Plugins"); + public static readonly string PluginsDirectory = Combine(GetDirectoryName(Assembly.GetEntryAssembly().Location), "Plugins"); private static readonly FileSystemWatcher configWatcher; - private static int suspend = 0; - protected static NeoSystem System { get; private set; } + public virtual string ConfigFile => Combine(PluginsDirectory, GetType().Assembly.GetName().Name, "config.json"); public virtual string Name => GetType().Name; + public string Path => Combine(PluginsDirectory, GetType().Assembly.ManifestModule.ScopeName); + protected static NeoSystem System { get; private set; } public virtual Version Version => GetType().Assembly.GetName().Version; - public virtual string ConfigFile => Path.Combine(pluginsPath, GetType().Assembly.GetName().Name, "config.json"); static Plugin() { - if (Directory.Exists(pluginsPath)) + if (Directory.Exists(PluginsDirectory)) { - configWatcher = new FileSystemWatcher(pluginsPath, "*.json") + configWatcher = new FileSystemWatcher(PluginsDirectory) { EnableRaisingEvents = true, IncludeSubdirectories = true, @@ -58,74 +59,112 @@ protected Plugin() Configure(); } - public abstract void Configure(); - - protected virtual void OnPluginsLoaded() + protected virtual void Configure() { } private static void ConfigWatcher_Changed(object sender, FileSystemEventArgs e) { - foreach (var plugin in Plugins) + switch (GetExtension(e.Name)) { - if (plugin.ConfigFile == e.FullPath) - { - plugin.Configure(); - plugin.Log($"Reloaded config for {plugin.Name}"); + case ".json": + Plugins.FirstOrDefault(p => p.ConfigFile == e.FullPath)?.Configure(); + break; + case ".dll": + if (e.ChangeType != WatcherChangeTypes.Created) return; + if (GetDirectoryName(e.FullPath) != PluginsDirectory) return; + try + { + LoadPlugin(Assembly.Load(File.ReadAllBytes(e.FullPath))); + } + catch { } break; - } } } + private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + if (args.Name.Contains(".resources")) + return null; + + Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name); + if (assembly != null) + return assembly; + + AssemblyName an = new AssemblyName(args.Name); + string filename = an.Name + ".dll"; + + try + { + return Assembly.Load(File.ReadAllBytes(filename)); + } + catch (Exception ex) + { + Utility.Log(nameof(Plugin), LogLevel.Error, $"Failed to resolve assembly or its dependency: {ex.Message}"); + return null; + } + } + + public virtual void Dispose() + { + } + protected IConfigurationSection GetConfiguration() { return new ConfigurationBuilder().AddJsonFile(ConfigFile, optional: true).Build().GetSection("PluginConfiguration"); } - internal static void LoadPlugins(NeoSystem system) + private static void LoadPlugin(Assembly assembly) { - System = system; - if (!Directory.Exists(pluginsPath)) return; - foreach (string filename in Directory.EnumerateFiles(pluginsPath, "*.dll", SearchOption.TopDirectoryOnly)) + foreach (Type type in assembly.ExportedTypes) { - var file = File.ReadAllBytes(filename); - Assembly assembly = Assembly.Load(file); - foreach (Type type in assembly.ExportedTypes) - { - if (!type.IsSubclassOf(typeof(Plugin))) continue; - if (type.IsAbstract) continue; + if (!type.IsSubclassOf(typeof(Plugin))) continue; + if (type.IsAbstract) continue; - ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes); - try - { - constructor?.Invoke(null); - } - catch (Exception ex) - { - Log(nameof(Plugin), LogLevel.Error, $"Failed to initialize plugin: {ex.Message}"); - } + ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes); + try + { + constructor?.Invoke(null); + } + catch (Exception ex) + { + Utility.Log(nameof(Plugin), LogLevel.Error, $"Failed to initialize plugin: {ex.Message}"); } } } - internal static void NotifyPluginsLoadedAfterSystemConstructed() + internal static void LoadPlugins(NeoSystem system) { - foreach (var plugin in Plugins) - plugin.OnPluginsLoaded(); + System = system; + if (!Directory.Exists(PluginsDirectory)) return; + List assemblies = new List(); + foreach (string filename in Directory.EnumerateFiles(PluginsDirectory, "*.dll", SearchOption.TopDirectoryOnly)) + { + try + { + assemblies.Add(Assembly.Load(File.ReadAllBytes(filename))); + } + catch { } + } + foreach (Assembly assembly in assemblies) + { + LoadPlugin(assembly); + } } protected void Log(string message, LogLevel level = LogLevel.Info) { - Log($"{nameof(Plugin)}:{Name}", level, message); + Utility.Log($"{nameof(Plugin)}:{Name}", level, message); } - public static void Log(string source, LogLevel level, string message) + protected virtual bool OnMessage(object message) { - foreach (ILogPlugin plugin in Loggers) - plugin.Log(source, level, message); + return false; } - protected virtual bool OnMessage(object message) => false; + internal protected virtual void OnPluginsLoaded() + { + } protected static bool ResumeNodeStartup() { @@ -148,32 +187,5 @@ protected static void SuspendNodeStartup() Interlocked.Increment(ref suspend); System.SuspendNodeStartup(); } - - private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) - { - if (args.Name.Contains(".resources")) - return null; - - Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name); - if (assembly != null) - return assembly; - - AssemblyName an = new AssemblyName(args.Name); - string filename = an.Name + ".dll"; - - try - { - return Assembly.LoadFrom(filename); - } - catch (Exception ex) - { - Log(nameof(Plugin), LogLevel.Error, $"Failed to resolve assembly or its dependency: {ex.Message}"); - return null; - } - } - - public virtual void Dispose() - { - } } } diff --git a/src/neo/Utility.cs b/src/neo/Utility.cs index 76c5b80794..b6402a91c6 100644 --- a/src/neo/Utility.cs +++ b/src/neo/Utility.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Configuration; using Neo.Cryptography.ECC; +using Neo.Plugins; using Neo.SmartContract; using Neo.Wallets; using System; @@ -72,5 +73,11 @@ public static IConfigurationRoot LoadConfig(string config) .AddJsonFile(configFile, true) .Build(); } + + public static void Log(string source, LogLevel level, string message) + { + foreach (ILogPlugin plugin in Plugin.Loggers) + plugin.Log(source, level, message); + } } } diff --git a/tests/neo.UnitTests/Ledger/UT_MemoryPool.cs b/tests/neo.UnitTests/Ledger/UT_MemoryPool.cs index 510f7340ed..38a456bcf3 100644 --- a/tests/neo.UnitTests/Ledger/UT_MemoryPool.cs +++ b/tests/neo.UnitTests/Ledger/UT_MemoryPool.cs @@ -19,7 +19,7 @@ namespace Neo.UnitTests.Ledger { internal class TestIMemoryPoolTxObserverPlugin : Plugin, IMemoryPoolTxObserverPlugin { - public override void Configure() { } + protected override void Configure() { } public void TransactionAdded(Transaction tx) { } public void TransactionsRemoved(MemoryPoolTxRemovalReason reason, IEnumerable transactions) { } } diff --git a/tests/neo.UnitTests/Plugins/TestLogPlugin.cs b/tests/neo.UnitTests/Plugins/TestLogPlugin.cs index d9f0c824d2..215715d236 100644 --- a/tests/neo.UnitTests/Plugins/TestLogPlugin.cs +++ b/tests/neo.UnitTests/Plugins/TestLogPlugin.cs @@ -9,9 +9,9 @@ public class TestLogPlugin : Plugin, ILogPlugin public string Output { set; get; } - public override void Configure() { } + protected override void Configure() { } - public new void Log(string source, LogLevel level, string message) + void ILogPlugin.Log(string source, LogLevel level, string message) { Output = source + "_" + level.ToString() + "_" + message; } diff --git a/tests/neo.UnitTests/Plugins/UT_Plugin.cs b/tests/neo.UnitTests/Plugins/UT_Plugin.cs index 7c3224c82a..cee7194c47 100644 --- a/tests/neo.UnitTests/Plugins/UT_Plugin.cs +++ b/tests/neo.UnitTests/Plugins/UT_Plugin.cs @@ -54,14 +54,6 @@ public void TestSendMessage() } } - [TestMethod] - public void TestNotifyPluginsLoadedAfterSystemConstructed() - { - var pp = new TestLogPlugin(); - Action action = () => Plugin.NotifyPluginsLoadedAfterSystemConstructed(); - action.Should().NotThrow(); - } - [TestMethod] public void TestResumeNodeStartupAndSuspendNodeStartup() {