Implement inter-plugin communication system + add some showcase of it

This commit is contained in:
ChronosX88 2020-09-28 21:49:09 +04:00
parent a829042581
commit bd74068d6f
Signed by: ChronosXYZ
GPG Key ID: 085A69A82C8C511A
11 changed files with 139 additions and 68 deletions

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
using System.Reflection;
namespace Zirconium.Core.Plugins.IPC
{
public class ExportedIPCMethod
{
public object Service { get; set; }
public string MethodName { get; set; }
public MethodInfo Method { get; set; }
}
}

View File

@ -0,0 +1,13 @@
namespace Zirconium.Core.Plugins.IPC
{
[System.AttributeUsage(System.AttributeTargets.Method)]
public class ExportedIPCMethodAttribute : System.Attribute
{
public string MethodName { get; private set; }
public ExportedIPCMethodAttribute(string name)
{
this.MethodName = name;
}
}
}

View File

@ -0,0 +1,53 @@
using System.Threading.Tasks;
using System.Linq;
using System;
using System.Collections.Generic;
using Zirconium.Utils;
namespace Zirconium.Core.Plugins.IPC
{
public class IPCRouter
{
private IDictionary<string, IList<ExportedIPCMethod>> methodTable = new Dictionary<string, IList<ExportedIPCMethod>>();
public void RegisterIPCService(string pluginName, object service)
{
Type t = service.GetType();
var exportedMethods = t.GetMethods()
.Where(m => m.GetCustomAttributes(typeof(ExportedIPCMethodAttribute), false).Length > 0)
.ToArray();
foreach (var m in exportedMethods)
{
var attr = (ExportedIPCMethodAttribute)m.GetCustomAttributes(typeof(ExportedIPCMethodAttribute), false).First();
var exportedMethod = new ExportedIPCMethod();
exportedMethod.Service = service;
exportedMethod.MethodName = attr.MethodName;
exportedMethod.Method = m;
if (methodTable.GetValueOrDefault(pluginName, null) == null)
{
methodTable[pluginName] = new List<ExportedIPCMethod>();
}
methodTable[pluginName].Add(exportedMethod);
}
}
public Task<dynamic> MakeRequest(string pluginName, string methodName, dynamic paramsObject)
{
return Task.Factory.StartNew<dynamic>(() =>
{
var method = methodTable[pluginName].Where(x => x.MethodName == methodName).FirstOrDefault();
var returnValue = method.Method.Invoke(method.Service, new object[] {paramsObject});
return returnValue;
});
}
public Task MakeNotif(string pluginName, string methodName, dynamic paramsObject)
{
return Task.Factory.StartNew(() =>
{
var method = methodTable[pluginName].Where(x => x.MethodName == methodName).FirstOrDefault();
method.Method.Invoke(method.Service, paramsObject);
});
}
}
}

View File

@ -7,7 +7,5 @@ namespace Zirconium.Core.Plugins.Interfaces
void Initialize(IPluginHostAPI hostModuleAPI);
void PreInitialize(IPluginManager pluginManager);
string GetPluginUniqueName();
dynamic GetExportedAPI();
Type[] GetExportedTypes();
}
}

View File

@ -1,3 +1,4 @@
using System.Threading.Tasks;
using Zirconium.Core.Models;
namespace Zirconium.Core.Plugins.Interfaces
@ -17,5 +18,8 @@ namespace Zirconium.Core.Plugins.Interfaces
dynamic GetSettings(string pluginName);
void ProvideAuth(IAuthProvider provider);
IExposedSessionManager GetSessionManager();
void RegisterIPCService(IPluginAPI plugin, dynamic service);
Task<dynamic> MakeIPCRequest(string pluginName, string methodName, dynamic paramsObject);
Task MakeIPCNotification(string pluginName, string methodName, dynamic paramsObject);
}
}

View File

@ -2,6 +2,6 @@ namespace Zirconium.Core.Plugins.Interfaces
{
public interface IPluginManager
{
dynamic Depends(IPluginAPI currentPlugin, string pluginName);
void Depends(IPluginAPI currentPlugin, string pluginName);
}
}

View File

@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Zirconium.Core.Models;
using Zirconium.Core.Plugins.Interfaces;
using Zirconium.Core.Plugins.IPC;
namespace Zirconium.Core.Plugins
{
@ -9,11 +12,13 @@ namespace Zirconium.Core.Plugins
{
private App _app;
private Router _router;
private IPCRouter _ipcRouter;
public PluginHostAPI(App app, Router router)
{
_router = router;
_app = app;
_ipcRouter = new IPCRouter();
}
public IExposedSessionManager GetSessionManager() {
@ -78,5 +83,18 @@ namespace Zirconium.Core.Plugins
{
_router.RemoveCoreEventHandler(handler.GetHandlingEventType(), handler);
}
public void RegisterIPCService(IPluginAPI plugin, dynamic service)
{
_ipcRouter.RegisterIPCService(plugin.GetPluginUniqueName(), service);
}
public Task<dynamic> MakeIPCRequest(string pluginName, string methodName, dynamic paramsObject) {
return _ipcRouter.MakeRequest(pluginName, methodName, paramsObject);
}
public Task MakeIPCNotification(string pluginName, string methodName, dynamic paramsObject) {
return _ipcRouter.MakeNotif(pluginName, methodName, paramsObject);
}
}
}

View File

@ -8,6 +8,7 @@ using McMaster.NETCore.Plugins;
using Zirconium.Core.Logging;
using Zirconium.Core.Models;
using Zirconium.Core.Plugins.Interfaces;
using Zirconium.Core.Plugins.IPC;
using Zirconium.Utils;
namespace Zirconium.Core.Plugins
@ -73,7 +74,8 @@ namespace Zirconium.Core.Plugins
typeof(IC2SMessageHandler),
typeof(ICoreEventHandler),
typeof(BaseMessage),
typeof(CoreEvent)
typeof(CoreEvent),
typeof(ExportedIPCMethodAttribute)
},
config => config.PreferSharedTypes = true
);
@ -92,28 +94,18 @@ namespace Zirconium.Core.Plugins
// This assumes the implementation of IPlugin has a parameterless constructor
plugin = (IPluginAPI)Activator.CreateInstance(pluginType);
Logging.Log.Debug($"Created plugin instance '{plugin.GetPluginUniqueName()}'.");
var exportedTypes = plugin.GetExportedTypes();
if (exportedTypes != null)
{
foreach (var type in exportedTypes)
{
var path = new Uri(type.Module.Assembly.CodeBase).LocalPath;
AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
}
}
plugin.PreInitialize(this);
plugin.Initialize(_pluginHostAPI);
}
return plugin;
}
public dynamic Depends(IPluginAPI currentPlugin, string pluginName)
public void Depends(IPluginAPI currentPlugin, string pluginName)
{
var dependantPlugin = _plugins.GetValueOrDefault(pluginName, null);
if (dependantPlugin != null) return dependantPlugin.GetExportedAPI();
if (dependantPlugin != null) return;
this.LoadPlugin(pluginName);
dependantPlugin = _plugins[pluginName];
return dependantPlugin.GetExportedAPI();
}
}
}

View File

@ -1,8 +1,10 @@
using System.Collections.Immutable;
using System;
using System.Collections.Immutable;
using System.Collections.Generic;
using Zirconium.Core.Plugins.Interfaces;
using MongoDB.Driver;
using Zirconium.Core.Logging;
using Zirconium.Core.Plugins.IPC;
namespace MongoDBPlugin
{
@ -10,18 +12,6 @@ namespace MongoDBPlugin
{
private IMongoDatabase _database;
public dynamic GetExportedAPI()
{
return _database;
}
public System.Type[] GetExportedTypes()
{
// var mongoCore = Assembly.Load("MongoDB.Driver.Core");
// var mongoBson = Assembly.Load("MongoDB.Bson");
return new System.Type[] {typeof(IMongoDatabase)};
}
public string GetPluginUniqueName()
{
return "MongoDB";
@ -30,13 +20,13 @@ namespace MongoDBPlugin
public void Initialize(IPluginHostAPI pluginHostAPI)
{
var settingsDynamic = pluginHostAPI.GetSettings(this);
var settings = (IImmutableDictionary<string, dynamic>) ((IDictionary<string, object>)settingsDynamic).ToImmutableDictionary();
var settings = (IImmutableDictionary<string, dynamic>)((IDictionary<string, object>)settingsDynamic).ToImmutableDictionary();
var host = (string) settings.GetValueOrDefault("Host");
var port = (int) settings.GetValueOrDefault("Port");
var user = (string) settings.GetValueOrDefault("User");
var password = (string) settings.GetValueOrDefault("Password");
var database = (string) settings.GetValueOrDefault("Database");
var host = (string)settings.GetValueOrDefault("Host");
var port = (int)settings.GetValueOrDefault("Port");
var user = (string)settings.GetValueOrDefault("User");
var password = (string)settings.GetValueOrDefault("Password");
var database = (string)settings.GetValueOrDefault("Database");
MongoClient client;
if (user == null && password == null)
@ -51,8 +41,25 @@ namespace MongoDBPlugin
var db = client.GetDatabase(database);
Log.Info("MongoDB is connected");
_database = db;
var ipcService = new IPCService(db);
pluginHostAPI.RegisterIPCService(this, ipcService);
}
public void PreInitialize(IPluginManager pluginManager) { }
class IPCService
{
private IMongoDatabase _db;
public IPCService(IMongoDatabase db) { _db = db; }
[ExportedIPCMethod("Insert")]
public void Insert(dynamic paramsObject) {
var colName = paramsObject.ColName;
var model = paramsObject.Model;
_db.GetCollection<dynamic>(colName).InsertOne(model);
Log.Debug("successfully inserted the document");
}
}
}
}

View File

@ -1,52 +1,29 @@
using Zirconium.Core.Plugins.Interfaces;
using MongoDB.Driver;
using Zirconium.Core.Logging;
using System;
using System.Dynamic;
using Zirconium.Core.Plugins.Interfaces;
namespace TestMongoDB
{
class Plugin : IPluginAPI
{
private IMongoDatabase _database;
public dynamic GetExportedAPI()
{
return null;
}
public Type[] GetExportedTypes()
{
return null;
}
public string GetPluginUniqueName()
{
return "TestMongoDB";
}
public void Initialize(IPluginHostAPI pluginHostAPI)
public async void Initialize(IPluginHostAPI pluginHostAPI)
{
var tm = new TestModel();
tm.ABC = "qweqowie";
_database.GetCollection<TestModel>("test_model").InsertOne(tm);
dynamic paramsObject = new ExpandoObject();
paramsObject.ColName = "test_model";
paramsObject.Model = tm;
await pluginHostAPI.MakeIPCRequest("MongoDB", "Insert", paramsObject);
}
public void PreInitialize(IPluginManager pluginManager)
{
var db = pluginManager.Depends(this, "MongoDB");
var receivedType = db.GetType();
Log.Debug(db.GetType().FullName);
Log.Debug(db.GetType().AssemblyQualifiedName);
foreach (Type i in db.GetType().GetInterfaces()) {
Log.Debug("DB object interface: " + i.AssemblyQualifiedName);
}
Log.Debug("Type info in current assembly context:");
var mongoDBType = typeof(IMongoDatabase);
Log.Debug(mongoDBType.FullName);
Log.Debug(mongoDBType.AssemblyQualifiedName);
Log.Debug(new Uri(mongoDBType.Module.Assembly.CodeBase).LocalPath);
Log.Debug($"Casting compatibility: {mongoDBType.IsAssignableFrom(db.GetType())}");
_database = (IMongoDatabase) db;
pluginManager.Depends(this, "MongoDB");
}
}

View File

@ -8,8 +8,6 @@
<ProjectReference Include="../../Zirconium/Zirconium.csproj">
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
<ProjectReference Include="../MongoDB/MongoDB.csproj"/>
</ItemGroup>
</Project>