diff --git a/KitX.Loader.CSharp/ArgsParser.cs b/KitX.Loader.CSharp/ArgsParser.cs index fbc6557..cbb2afc 100644 --- a/KitX.Loader.CSharp/ArgsParser.cs +++ b/KitX.Loader.CSharp/ArgsParser.cs @@ -7,18 +7,26 @@ public class ArgsParser public static void Parse(string[] args) { Parser.Default.ParseArguments(args) - .WithParsed(option => + .WithParsed(async option => { + if (option.PluginPath is null) + return; + + if (option.WorkingDirectory is not null) + Directory.SetCurrentDirectory(option.WorkingDirectory); + var communicationManager = new CommunicationManager(); - if (option.DashboardIpAddress is not null) - communicationManager = communicationManager - .Connect(option.DashboardIpAddress ?? ""); + if (option.ConnectUrl is not null) + communicationManager = await communicationManager.Connect(option.ConnectUrl); else communicationManager = null; var pluginManager = new PluginManager() - .OnSendMessage(x => communicationManager?.SendMessage(x)) - .LoadPlugin(option.PluginPath ?? ""); + .OnSendMessage(x => communicationManager?.SendMessageAsync(x)) + .LoadPlugin(option.PluginPath); + + if (communicationManager is not null) + communicationManager.OnReceiveMessage = x => pluginManager.ReceiveMessage(x); }); } } diff --git a/KitX.Loader.CSharp/CommunicationManager.cs b/KitX.Loader.CSharp/CommunicationManager.cs index e6a2b34..f1b8c99 100644 --- a/KitX.Loader.CSharp/CommunicationManager.cs +++ b/KitX.Loader.CSharp/CommunicationManager.cs @@ -1,116 +1,108 @@ -using System.Net.Sockets; +using System.Net.WebSockets; using System.Text; namespace KitX.Loader.CSharp; public class CommunicationManager { - private readonly TcpClient? client; - - private readonly Thread? receiveThread; - - private bool stillReceiving = true; + private readonly ClientWebSocket? Client; private int receiveBufferSize = 1024 * 1024 * 10; // 10MB + public Action? OnReceiveMessage { get; set; } + public CommunicationManager() { - client = new(); + Client = new(); - receiveThread = new(ReceiveMessage); + Client.Options.KeepAliveInterval = TimeSpan.FromSeconds(10); } - public CommunicationManager Connect(string address) + public async Task Connect(string? url) { - var splited = address.Split(':'); + ArgumentNullException.ThrowIfNull(url, nameof(url)); - var ipv4 = splited[0]; + ArgumentNullException.ThrowIfNull(Client, nameof(Client)); - if (!int.TryParse(splited[1], out var port)) - throw new ArgumentException("Bad port number.", nameof(address)); + await Client.ConnectAsync(new Uri(url), CancellationToken.None); - client?.Connect(ipv4, port); + var waiting = true; - receiveThread?.Start(); + while (waiting) + { + switch (Client.State) + { + case WebSocketState.None: + waiting = false; + break; + case WebSocketState.Connecting: + break; + case WebSocketState.Open: + new Thread(async () => await ReceiveAsync()).Start(); + waiting = false; + break; + case WebSocketState.CloseSent: + waiting = false; + break; + case WebSocketState.CloseReceived: + waiting = false; + break; + case WebSocketState.Closed: + waiting = false; + break; + case WebSocketState.Aborted: + waiting = false; + break; + } + } return this; } - public CommunicationManager SendMessage(string message) + public async Task SendMessageAsync(string message) { - var stream = client?.GetStream(); - - if (stream is null) return this; + ArgumentNullException.ThrowIfNull(Client, nameof(Client)); var data = Encoding.UTF8.GetBytes(message); - try - { - stream?.Write(data, 0, data.Length); + var bufferToSend = new ArraySegment(data); - stream?.Flush(); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - - Console.WriteLine(ex.StackTrace); - - stream?.Close(); - - stream?.Dispose(); - } + await Client.SendAsync(bufferToSend, WebSocketMessageType.Text, true, CancellationToken.None); return this; } - private void ReceiveMessage() + private async Task ReceiveAsync() { - if (client is null) return; - - var stream = client?.GetStream(); + ArgumentNullException.ThrowIfNull(Client, nameof(Client)); - if (stream is null) return; + var buffer = new byte[receiveBufferSize]; - var buffer = new byte[receiveBufferSize]; // Default 10 MB buffer - - try + while (true) { - while (stillReceiving) - { + var receivedBuffer = new ArraySegment(buffer); - if (buffer is null) break; + var result = await Client.ReceiveAsync( + receivedBuffer, + CancellationToken.None + ); - var length = stream.Read(buffer, 0, buffer.Length); - - if (length > 0) - { - var msg = Encoding.UTF8.GetString(buffer, 0, length); - - //ToDo: Process `msg` - } - else - { - stream?.Dispose(); + if (result.MessageType == WebSocketMessageType.Close) + { + await Client.CloseAsync( + WebSocketCloseStatus.NormalClosure, + string.Empty, + CancellationToken.None + ); - break; - } + break; } - stream?.Close(); + var message = Encoding.UTF8.GetString(buffer, 0, result.Count); - stream?.Dispose(); - } - catch (Exception e) - { - Console.WriteLine(e.Message); - Console.WriteLine(e.StackTrace); + OnReceiveMessage?.Invoke(message); - stream?.Close(); - stream?.Dispose(); - - client?.Close(); - client?.Dispose(); + if (!result.EndOfMessage) continue; } } @@ -121,9 +113,15 @@ public CommunicationManager SetBufferSize(int size) return this; } - public CommunicationManager Stop() + public async Task Close() { - stillReceiving = false; + ArgumentNullException.ThrowIfNull(Client, nameof(Client)); + + await Client.CloseAsync( + WebSocketCloseStatus.NormalClosure, + string.Empty, + CancellationToken.None + ); return this; } diff --git a/KitX.Loader.CSharp/KitX.Loader.CSharp.csproj b/KitX.Loader.CSharp/KitX.Loader.CSharp.csproj index e6c3e49..83cc03b 100644 --- a/KitX.Loader.CSharp/KitX.Loader.CSharp.csproj +++ b/KitX.Loader.CSharp/KitX.Loader.CSharp.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable @@ -10,12 +10,11 @@ - - - + + diff --git a/KitX.Loader.CSharp/Options.cs b/KitX.Loader.CSharp/Options.cs index 9918135..d63e635 100644 --- a/KitX.Loader.CSharp/Options.cs +++ b/KitX.Loader.CSharp/Options.cs @@ -7,6 +7,9 @@ public class Options [Option('l', "load", Required = true, HelpText = "Path to plugin.")] public string? PluginPath { get; set; } - [Option('c', "connect", Required = false, HelpText = "Dashboard IPv4 address.")] - public string? DashboardIpAddress { get; set; } + [Option('c', "connect", Required = false, HelpText = "Connect url.")] + public string? ConnectUrl { get; set; } + + [Option("working-directory", Required = false, HelpText = "Set working directory.")] + public string? WorkingDirectory { get; set; } } diff --git a/KitX.Loader.CSharp/PluginManager.cs b/KitX.Loader.CSharp/PluginManager.cs index 1c4f1a1..c0ff6de 100644 --- a/KitX.Loader.CSharp/PluginManager.cs +++ b/KitX.Loader.CSharp/PluginManager.cs @@ -1,19 +1,46 @@ using KitX.Contract.CSharp; -using KitX.Web.Rules; +using KitX.Shared.Plugin; +using KitX.Shared.WebCommand; +using KitX.Shared.WebCommand.Details; +using KitX.Shared.WebCommand.Infos; using System.ComponentModel.Composition.Hosting; using System.Reflection; +using System.Text; using System.Text.Json; namespace KitX.Loader.CSharp; public class PluginManager { - private PluginStruct? pluginStruct; + private PluginInfo? pluginInfo; private IController? controller; private Action? sendMessageAction; + private readonly Connector Connector = Connector.Instance; + + private static readonly JsonSerializerOptions serializerOptions = new() + { + WriteIndented = true, + IncludeFields = true, + PropertyNameCaseInsensitive = true, + }; + + public PluginManager() + { + Connector = Connector + .SetSender( + x => SendMessage( + JsonSerializer.Serialize(x, serializerOptions) + ) + ) + .SetSerializer( + x => JsonSerializer.Serialize(x, serializerOptions) + ); + ; + } + public PluginManager OnSendMessage(Action action) { sendMessageAction = action; @@ -27,6 +54,7 @@ public PluginManager LoadPlugin(string path) throw new ArgumentException("File not exist.", nameof(path)); var dirPath = Path.GetDirectoryName(path); + var fileName = Path.GetFileName(path); if (dirPath is null) throw new Exception("Can't get directory path of plugin file."); @@ -39,56 +67,58 @@ public PluginManager LoadPlugin(string path) var sub = container.GetExportedValues(); - foreach (var item in sub) - { - InitPlugin(item, path); - break; - } + InitPlugin(sub.First()); return this; } - private void InitPlugin(IIdentityInterface plugin, string path) + private void InitPlugin(IIdentityInterface plugin) { - RegisterPluginStruct(plugin); + pluginInfo = plugin.GetPluginInfo(); + + var pluginInfoToSend = Encoding.UTF8.GetBytes( + JsonSerializer.Serialize(pluginInfo, serializerOptions) + ); - SendMessage($"PluginStruct: {JsonSerializer.Serialize(pluginStruct)}"); + Connector.Request().RegisterPlugin(pluginInfoToSend, pluginInfoToSend.Length).Send(); controller = plugin.GetController(); - controller.SetRootPath(Path.GetDirectoryName(path)); controller.SetSendCommandAction( - x => SendMessage($"PluginCommand: {JsonSerializer.Serialize(x)}") + x => SendMessage(JsonSerializer.Serialize(x, serializerOptions)) ); controller.Start(); } - private void RegisterPluginStruct(IIdentityInterface identity) + private void SendMessage(string message) => sendMessageAction?.Invoke(message); + + public void ReceiveMessage(string message) { - pluginStruct = new() + var kwc = JsonSerializer.Deserialize(message, serializerOptions); + + var command = JsonSerializer.Deserialize(kwc.Content, serializerOptions); + + switch (command.Request) { - Name = identity.GetName(), - Version = identity.GetVersion(), - DisplayName = identity.GetDisplayName(), - AuthorName = identity.GetAuthorName(), - PublisherName = identity.GetPublisherName(), - AuthorLink = identity.GetAuthorLink(), - PublisherLink = identity.GetPublisherLink(), - SimpleDescription = identity.GetSimpleDescription(), - ComplexDescription = identity.GetComplexDescription(), - TotalDescriptionInMarkdown = identity.GetTotalDescriptionInMarkdown(), - IconInBase64 = identity.GetIconInBase64(), - PublishDate = identity.GetPublishDate(), - LastUpdateDate = identity.GetLastUpdateDate(), - IsMarketVersion = identity.IsMarketVersion(), - Tags = new(), - Functions = identity.GetController().GetFunctions(), - RootStartupFileName = identity.GetRootStartupFileName(), - }; - } + case CommandRequestInfo.ReceiveWorkingDetail: - private void SendMessage(string message) => sendMessageAction?.Invoke(message); + var workingDetailJson = Encoding.UTF8.GetString(command.Body); + + var workingDetail = JsonSerializer.Deserialize(workingDetailJson); + + if (workingDetail is not null) + controller?.SetWorkingDetail(workingDetail); + + break; + + case CommandRequestInfo.ReceiveCommand: + + controller?.Execute(command); + + break; + } + } private static void AddDllResolveHandler(string appendPath) { diff --git a/KitX.Loader.WPF.Core/App.xaml.cs b/KitX.Loader.WPF.Core/App.xaml.cs index 299b6dd..1b44189 100644 --- a/KitX.Loader.WPF.Core/App.xaml.cs +++ b/KitX.Loader.WPF.Core/App.xaml.cs @@ -14,8 +14,12 @@ private void Application_Startup(object sender, StartupEventArgs e) } catch (Exception o) { - MessageBox.Show(o.Message, "Loader Error", - MessageBoxButton.OK, MessageBoxImage.Error); + MessageBox.Show( + o.Message, + "Loader Error", + MessageBoxButton.OK, + MessageBoxImage.Error + ); Console.WriteLine(o.Message); diff --git a/KitX.Loader.WPF.Core/KitX.Loader.WPF.Core.csproj b/KitX.Loader.WPF.Core/KitX.Loader.WPF.Core.csproj index 4ab15c8..09cfc24 100644 --- a/KitX.Loader.WPF.Core/KitX.Loader.WPF.Core.csproj +++ b/KitX.Loader.WPF.Core/KitX.Loader.WPF.Core.csproj @@ -2,7 +2,7 @@ WinExe - net7.0-windows + net8.0-windows enable true diff --git a/KitX.Loader.WPF.Core/dashboard_debug.ps1 b/KitX.Loader.WPF.Core/dashboard_debug.ps1 new file mode 100644 index 0000000..8410429 --- /dev/null +++ b/KitX.Loader.WPF.Core/dashboard_debug.ps1 @@ -0,0 +1,18 @@ +param( + [int] + $From = 1, + + [int] + $To = $(throw "`To` not provide"), + + [int] + $Port = $(throw "`Port` not provide"), + + [double] + $Sleep = 0.7 +) + +$From..$To | ForEach-Object { + .\bin\Debug\net8.0-windows\KitX.Loader.WPF.Core.exe --load '..\..\KitX Plugins\TestPlugin.WPF.Core\bin\Debug\net8.0-windows\TestPlugin.WPF.Core.dll' --connect "ws://127.0.0.1:$Port/connectionId_$_"; + Start-Sleep $Sleep; +} diff --git a/KitX.Loader.Winform.Core/KitX.Loader.Winform.Core.csproj b/KitX.Loader.Winform.Core/KitX.Loader.Winform.Core.csproj index 2d6ebba..5b397b9 100644 --- a/KitX.Loader.Winform.Core/KitX.Loader.Winform.Core.csproj +++ b/KitX.Loader.Winform.Core/KitX.Loader.Winform.Core.csproj @@ -2,7 +2,7 @@ WinExe - net7.0-windows + net8.0-windows enable true enable diff --git a/KitX.Loader.Winform.Core/Program.cs b/KitX.Loader.Winform.Core/Program.cs index bc4d581..7c20849 100644 --- a/KitX.Loader.Winform.Core/Program.cs +++ b/KitX.Loader.Winform.Core/Program.cs @@ -14,8 +14,6 @@ static void Main() // see https://aka.ms/applicationconfiguration. ApplicationConfiguration.Initialize(); - //Application.Run(new Form1()); - ArgsParser.Parse(Environment.GetCommandLineArgs()); } }