Skip to content

Commit

Permalink
Merge pull request #1 from RubenPX/Tauri-Plugins-C#
Browse files Browse the repository at this point in the history
Plugin Route Funcionality (Structuring and serializing comunication)
  • Loading branch information
RubenPX authored Mar 12, 2024
2 parents bdce6cb + e0171eb commit f92e98a
Show file tree
Hide file tree
Showing 15 changed files with 390 additions and 70 deletions.
29 changes: 29 additions & 0 deletions src-netcore/TauriComunication/AssemblyDependency.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace TauriComunication {
public class AssemblyDependency {

public static Assembly? AssemblyResolve(object sender, ResolveEventArgs args, string pluginName) {
if (args.Name.StartsWith("System")) return null;

String DLLName = new AssemblyName(args.Name).Name + ".dll";

try {
var compatible = AppDomain.CurrentDomain.GetAssemblies().Where(asm => asm.FullName == args.Name).First();
Console.WriteLine($"Requested loaded ASM, returning {compatible.GetName()}");
return compatible;
} catch (Exception) {
}

string currentDirectory = Directory.GetCurrentDirectory();
string dependencyPath = Path.Combine(currentDirectory, "plugins", pluginName, "Dependencies", DLLName);

return Assembly.LoadFile(dependencyPath);
}
}
}
73 changes: 73 additions & 0 deletions src-netcore/TauriComunication/PluginInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using TauriComunication.Route;

namespace TauriComunication {
public class PluginInfo {
public string dllPath { get; private set; }
public Assembly assembly { get; private set; }
public Type[] types { get; private set; }
public List<MethodInfo> methods { get; private set; } = new List<MethodInfo>();

public string PluginName { get; private set; }

static byte[] loadFile(string filename) {
FileStream fs = new FileStream(filename, FileMode.Open);
byte[] buffer = new byte[(int)fs.Length];
fs.Read(buffer, 0, buffer.Length);
fs.Close();

return buffer;
}

public PluginInfo(string dllPath) {
this.assembly = AppDomain.CurrentDomain.Load(loadFile(dllPath));
this.PluginName = assembly.GetName().Name;

AppDomain.CurrentDomain.AssemblyResolve += (object? sender, ResolveEventArgs args) => AssemblyDependency.AssemblyResolve(sender, args, this.PluginName);
Debugger.Launch();

Console.WriteLine($"Loaded dll {Path.GetFileNameWithoutExtension(dllPath)}. Name: {PluginName}");

this.types = this.assembly.GetTypes();

foreach (var type in this.types) {

//Console.WriteLine($"Class: {type.Name}");
//foreach (var item in type.GetMethods(BindingFlags.Static | BindingFlags.Public)) {
// string methds = string.Join(',', item.GetParameters().Select(t => $"{t.Name}, {t.ParameterType.FullName}"));
// Console.WriteLine($"Method: {item.Name}, {item.Attributes}, {methds}");
//}

var compatibleMethods = type.GetMethods(BindingFlags.Static | BindingFlags.Public).Where(m => {
// Verify custom RouteMethodAttribute atribute
if (m.GetCustomAttribute<RouteMethodAttribute>() == null) return false;

// Verify parameters
ParameterInfo[] ps = m.GetParameters();
if (ps.Length != 1 && ps.Length != 2) return false;
if (ps[0].ParameterType.FullName != typeof(RouteRequest).FullName) return false;
if (ps[1] != null && ps[1].ParameterType.FullName != typeof(RouteResponse).FullName) return false;

// verify return
if (m.ReturnType.FullName != typeof(RouteResponse).FullName) return false;

return true;
}).ToArray();

foreach (var mthd in compatibleMethods) {
Console.WriteLine($"RouteMethod found: {mthd.GetType().Name}: {mthd.Name}");
}

this.methods.AddRange(compatibleMethods);
}
}

public MethodInfo? GetMethodName(string name) => methods.FirstOrDefault(m => m.Name == name, null);
}
}
106 changes: 106 additions & 0 deletions src-netcore/TauriComunication/PluginManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Diagnostics;
using System.Reflection;
using System.Text.Json.Serialization;
using TauriComunication.Route;

namespace TauriComunication {
public class PluginManager {
private static PluginManager instance;

private readonly PluginInfo[] plugins;

private PluginManager() {
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => AssemblyDependency.AssemblyResolve(sender, args, Directory.GetCurrentDirectory());
this.plugins = this.loadPluginsRoutes().ToArray();
}

private List<PluginInfo> loadPluginsRoutes() {
var pluginsDirectory = Path.Combine(Directory.GetCurrentDirectory(), "plugins");

if (!Directory.Exists(pluginsDirectory)) {
Console.WriteLine("Plugins directory does not exist");
return [];
}

string[] dllList = Directory.GetFiles(pluginsDirectory, "*.plugin.dll");

// Instance with list of nulls
List<PluginInfo> plugins = new List<PluginInfo>();

foreach (var dllPath in dllList) {
try {
plugins.Add(new PluginInfo(dllPath));
} catch (Exception ex) {
Console.WriteLine($"Failed to load {Path.GetFileName(dllPath)}: {ex.Message}");
}
}

return plugins;
}

internal RouteResponse handleRoute(RouteRequest routeRequest) {
RouteResponse preResponse = new RouteResponse() { id = routeRequest.id };

if (routeRequest == null) return preResponse.Error("Object RouteRequest is required");
if (routeRequest.id == null) return preResponse.Error("String parameter id is required");
if (routeRequest.plugin == null) return preResponse.Error("string parameter plugin is required");
if (routeRequest.method == null) return preResponse.Error("string parameter method is required");

// Convert to object
if (routeRequest.data.GetType().FullName == typeof(JObject).FullName) routeRequest.data = ((JObject)routeRequest.data).ToObject(typeof(object));

PluginInfo? foundPlugin = null;
try {
foundPlugin = this.plugins.Where(x => x.PluginName == routeRequest.plugin || x.PluginName == $"{routeRequest.plugin}.plugin").First();
} catch (InvalidOperationException) {
Console.WriteLine($"[PluginManager] Plugin {routeRequest.plugin} not found...");
return preResponse.Error($"Plugin {routeRequest.plugin} not found...");
}
MethodInfo? foundMethod;
try {
foundMethod = foundPlugin.methods.Where(m => m.Name == routeRequest.method).First();
} catch (InvalidOperationException) {
Console.WriteLine($"[{routeRequest.plugin}] Method {routeRequest.method} not found...");
return preResponse.Error($"Method {routeRequest.method} not found...");
}

try {
return (RouteResponse?)foundMethod.Invoke(null, [routeRequest, preResponse]); ;
} catch (Exception ex) {
Console.WriteLine($"[{routeRequest.plugin}][{routeRequest.method}] error: {ex.ToString()}");
return preResponse.Error($"{ex.Message}");
}
}

/// <summary>
/// This method handle all requests and redirect to any RouteHandler
/// </summary>
/// <param name="inputData"></param>
/// <returns></returns>
public static string processRequest(string? inputData) {
if (inputData is null or "") return JsonConvert.SerializeObject(new RouteResponse() { error = "Input is empty..." });
if (instance == null) instance = new PluginManager();

// If is init, avoid send process
if (inputData is "INIT") return JsonConvert.SerializeObject(new RouteResponse() { data = "OK!" });

RouteRequest request;

try {
request = JsonConvert.DeserializeObject<RouteRequest>(inputData);
} catch (Exception) {
return JsonConvert.SerializeObject(new RouteResponse() { error = "Failed to parse request JSON" });
}

try {
RouteResponse response = instance.handleRoute(request);
return JsonConvert.SerializeObject(response);
} catch (Exception ex) {
Console.WriteLine($"[PluginManager] Failed to process request. {ex.Message}");
return JsonConvert.SerializeObject(new RouteResponse() { error = $"Failed to process request. {ex.Message}" });
}
}
}
}
19 changes: 19 additions & 0 deletions src-netcore/TauriComunication/Route/RouteMethodAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace TauriComunication.Route;

/// <summary>
/// This atribute specifies entrypoint of any Route handler. This is searched by reflexion
/// <br /><br />
/// <code>
/// [<see cref="RouteHandler"/>]<br />
/// public static <see cref="RouteResponse"/> methodName(<see cref="RouteRequest"/> route)
/// </code>
/// </summary>
public class RouteMethodAttribute : Attribute {
public string? methodName { get; }

public RouteMethodAttribute(string methodName) {
this.methodName = methodName;
}

public RouteMethodAttribute() {}
}
10 changes: 10 additions & 0 deletions src-netcore/TauriComunication/Route/RouteRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Text.Json;

namespace TauriComunication.Route;

public class RouteRequest {
public string id;
public string plugin;
public string method;
public object? data;
}
19 changes: 19 additions & 0 deletions src-netcore/TauriComunication/Route/RouteResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace TauriComunication.Route;

public class RouteResponse {
public string id;
public string? error;
public object? data;

internal RouteResponse() { }

public RouteResponse Ok(object? data) {
this.data = data;
return this;
}

public RouteResponse Error(string error) {
this.data = error;
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

<ItemGroup>
Expand Down
15 changes: 15 additions & 0 deletions src-netcore/TauriComunication/Utils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TauriComunication {
public class Utils {
public static T ParseObject<T>(object data) {
if (data == null) throw new ArgumentNullException("data");
return ((JObject)data).ToObject<T>()!;
}
}
}
37 changes: 15 additions & 22 deletions src-netcore/TauriLib/CString.cs
Original file line number Diff line number Diff line change
@@ -1,30 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using System.Runtime.InteropServices;

namespace TauriIPC;
namespace TauriIPC;

public static class CString {
private static unsafe delegate*<char*, int, byte*> CopyToCString;
private static unsafe delegate*<char*, int, byte*> CopyToCString;

[UnmanagedCallersOnly]
public static unsafe void SetCopyToCStringFunctionPtr(delegate*<char*, int, byte*> copyToCString) => CopyToCString = copyToCString;
[UnmanagedCallersOnly]
public static unsafe void SetCopyToCStringFunctionPtr(delegate*<char*, int, byte*> copyToCString) => CopyToCString = copyToCString;

/* This function parse string from a region from memory an return a string region from a pointer */
[UnmanagedCallersOnly]
public unsafe static byte* runUTF8(/* byte* */IntPtr textPtr, int textLength) {
var text = Marshal.PtrToStringUTF8(textPtr, textLength);
if (text == null || text.Length == 0) text = null;
/* This function parse string from a region from memory an return a string region from a pointer */
[UnmanagedCallersOnly]
public static unsafe byte* runUTF8(/* byte* */IntPtr textPtr, int textLength) {
var text = Marshal.PtrToStringUTF8(textPtr, textLength);
if (text == null || text.Length == 0) text = null;

Console.WriteLine(text);
var loginText = TestApp.Main.login(text);
var loginText = TauriComunication.PluginManager.processRequest(text);

fixed (char* ptr = loginText) {
return CopyToCString(ptr, loginText.Length);
}
}
fixed (char* ptr = loginText) {
return CopyToCString(ptr, loginText.Length);
}
}
}
7 changes: 6 additions & 1 deletion src-netcore/TauriLib/TauriIPC.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
<Nullable>enable</Nullable>
<GenerateRuntimeConfigurationFiles>True</GenerateRuntimeConfigurationFiles>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\TestApp\TestApp.csproj" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\TauriComunication\TauriComunication.csproj" />
</ItemGroup>

</Project>
Loading

0 comments on commit f92e98a

Please sign in to comment.