Added Socket support
This commit is contained in:
@@ -121,6 +121,7 @@ public class Program
|
|||||||
await Installer.GenerateStartupConfig();
|
await Installer.GenerateStartupConfig();
|
||||||
|
|
||||||
Application.InitializeThreadedApi();
|
Application.InitializeThreadedApi();
|
||||||
|
Application.InitializeThreadedSockets();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
DiscordBotCore/API/ConnectionDetails.cs
Normal file
16
DiscordBotCore/API/ConnectionDetails.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using DiscordBotCore.Interfaces.API;
|
||||||
|
|
||||||
|
namespace DiscordBotCore.API;
|
||||||
|
|
||||||
|
public class ConnectionDetails : IConnectionDetails
|
||||||
|
{
|
||||||
|
public string Host { get; }
|
||||||
|
public int Port { get; }
|
||||||
|
|
||||||
|
public ConnectionDetails(string host, int port)
|
||||||
|
{
|
||||||
|
Host = host;
|
||||||
|
Port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ using DiscordBotCore.Interfaces.API;
|
|||||||
using DiscordBotCore.Others;
|
using DiscordBotCore.Others;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
namespace DiscordBotCore.API;
|
namespace DiscordBotCore.API.Endpoints;
|
||||||
|
|
||||||
public class ApiManager
|
public class ApiManager
|
||||||
{
|
{
|
||||||
@@ -52,7 +52,7 @@ public class ApiManager
|
|||||||
return this.ApiEndpoints.Exists(endpoint => endpoint.Path == endpointPath);
|
return this.ApiEndpoints.Exists(endpoint => endpoint.Path == endpointPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InitializeApi()
|
public async void InitializeApi()
|
||||||
{
|
{
|
||||||
if (IsRunning)
|
if (IsRunning)
|
||||||
return;
|
return;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DiscordBotCore.Others;
|
using DiscordBotCore.Others;
|
||||||
|
|
||||||
namespace DiscordBotCore.API;
|
namespace DiscordBotCore.API.Endpoints;
|
||||||
|
|
||||||
public class ApiResponse
|
public class ApiResponse
|
||||||
{
|
{
|
||||||
@@ -4,7 +4,7 @@ using DiscordBotCore.Interfaces.API;
|
|||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace DiscordBotCore.API;
|
namespace DiscordBotCore.API.Endpoints;
|
||||||
|
|
||||||
internal sealed class EndpointManager
|
internal sealed class EndpointManager
|
||||||
{
|
{
|
||||||
129
DiscordBotCore/API/Sockets/SocketManager.cs
Normal file
129
DiscordBotCore/API/Sockets/SocketManager.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.WebSockets;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using DiscordBotCore.API.Sockets.Sockets;
|
||||||
|
using DiscordBotCore.Interfaces.API;
|
||||||
|
|
||||||
|
namespace DiscordBotCore.API.Sockets;
|
||||||
|
|
||||||
|
internal class SocketManager
|
||||||
|
{
|
||||||
|
private readonly IConnectionDetails _ConnectionDetails;
|
||||||
|
private List<ISocket> _Sockets = new List<ISocket>();
|
||||||
|
|
||||||
|
public SocketManager(IConnectionDetails connectionDetails)
|
||||||
|
{
|
||||||
|
_ConnectionDetails = connectionDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterBaseSockets()
|
||||||
|
{
|
||||||
|
Register(new PluginDownloadProgressSocket());
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Register(ISocket socket)
|
||||||
|
{
|
||||||
|
if (_Sockets.Any(s => s.Path == socket.Path))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_Sockets.Add(socket);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
Console.WriteLine("Starting sockets ...");
|
||||||
|
foreach (var socket in _Sockets)
|
||||||
|
{
|
||||||
|
Thread thread = new Thread(() => StartSocket(socket));
|
||||||
|
thread.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void StartSocket(ISocket socket)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!socket.Path.StartsWith("/"))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Socket path '{socket.Path}' must start with '/'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
string prefix = $"http://{_ConnectionDetails.Host}:{_ConnectionDetails.Port}{socket.Path}/";
|
||||||
|
Console.WriteLine($"Starting socket with prefix: {prefix}");
|
||||||
|
|
||||||
|
HttpListener listener = new HttpListener();
|
||||||
|
listener.Prefixes.Add(prefix);
|
||||||
|
listener.Start();
|
||||||
|
|
||||||
|
await ConnectionHandler(listener, socket.HandleRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async Task ConnectionHandler(HttpListener listener, Func<byte[], int, Task<SocketResponse>> handler)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var context = await listener.GetContextAsync();
|
||||||
|
|
||||||
|
if (context.Request.IsWebSocketRequest)
|
||||||
|
{
|
||||||
|
WebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null);
|
||||||
|
Application.CurrentApplication.Logger.Log("WebSocket connection established.");
|
||||||
|
await HandleSocket(webSocketContext.WebSocket, handler);
|
||||||
|
}
|
||||||
|
else { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleSocket(WebSocket socket, Func<byte[], int, Task<SocketResponse>> handler)
|
||||||
|
{
|
||||||
|
if (socket.State != WebSocketState.Open)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] buffer = new byte[1024 * 4];
|
||||||
|
var receivedData = new List<byte>();
|
||||||
|
WebSocketReceiveResult result;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||||
|
receivedData.AddRange(buffer.Take(result.Count));
|
||||||
|
} while (!result.EndOfMessage);
|
||||||
|
|
||||||
|
if (result.MessageType == WebSocketMessageType.Close)
|
||||||
|
{
|
||||||
|
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing connection ...", CancellationToken.None);
|
||||||
|
Application.CurrentApplication.Logger.Log("WebSocket connection closed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Application.CurrentApplication.Logger.Log("WebSocket message received. Length: " + receivedData.Count);
|
||||||
|
SocketResponse socketResponse = await handler(receivedData.ToArray(), receivedData.Count);
|
||||||
|
ArraySegment<byte> response = new ArraySegment<byte>(socketResponse.Data, 0, socketResponse.Data.Length);
|
||||||
|
byte[]? lastResponse = null;
|
||||||
|
|
||||||
|
while (!socketResponse.CloseConnectionAfterResponse)
|
||||||
|
{
|
||||||
|
if (lastResponse == null || !socketResponse.Data.SequenceEqual(lastResponse))
|
||||||
|
{
|
||||||
|
await socket.SendAsync(response, WebSocketMessageType.Text, socketResponse.EndOfMessage, CancellationToken.None);
|
||||||
|
lastResponse = socketResponse.Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
socketResponse = await handler(receivedData.ToArray(), receivedData.Count);
|
||||||
|
response = new ArraySegment<byte>(socketResponse.Data, 0, socketResponse.Data.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing connection ...", CancellationToken.None);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
38
DiscordBotCore/API/Sockets/SocketResponse.cs
Normal file
38
DiscordBotCore/API/Sockets/SocketResponse.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
namespace DiscordBotCore.API.Sockets;
|
||||||
|
|
||||||
|
internal class SocketResponse
|
||||||
|
{
|
||||||
|
public byte[] Data { get;}
|
||||||
|
public bool EndOfMessage { get; }
|
||||||
|
public bool Success { get; }
|
||||||
|
public bool CloseConnectionAfterResponse { get; set; }
|
||||||
|
|
||||||
|
private SocketResponse(byte[] data, bool endOfMessage, bool success, bool closeConnectionAfterResponse)
|
||||||
|
{
|
||||||
|
Data = data;
|
||||||
|
EndOfMessage = endOfMessage;
|
||||||
|
Success = success;
|
||||||
|
CloseConnectionAfterResponse = closeConnectionAfterResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static SocketResponse From(byte[] data, bool endOfMessage, bool success, bool closeConnectionAfterResponse)
|
||||||
|
{
|
||||||
|
return new SocketResponse(data, endOfMessage, success, closeConnectionAfterResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static SocketResponse From(byte[] data, bool endOfMessage)
|
||||||
|
{
|
||||||
|
return new SocketResponse(data, endOfMessage, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static SocketResponse From(byte[] data)
|
||||||
|
{
|
||||||
|
return new SocketResponse(data, true, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static SocketResponse Fail()
|
||||||
|
{
|
||||||
|
return new SocketResponse(new byte[0], true, false, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using DiscordBotCore.Interfaces.API;
|
||||||
|
|
||||||
|
namespace DiscordBotCore.API.Sockets.Sockets;
|
||||||
|
|
||||||
|
internal class PluginDownloadProgressSocket : ISocket
|
||||||
|
{
|
||||||
|
private float value = 0.0f;
|
||||||
|
public string Path => "/plugin/download/progress";
|
||||||
|
public Task<SocketResponse> HandleRequest(byte[] request, int count)
|
||||||
|
{
|
||||||
|
value += 0.1f;
|
||||||
|
string pluginName = Encoding.UTF8.GetString(request, 0, count);
|
||||||
|
Application.CurrentApplication.Logger.Log($"Received plugin download progress for {pluginName}.");
|
||||||
|
SocketResponse response = SocketResponse.From(Encoding.UTF8.GetBytes(value.ToString()));
|
||||||
|
response.CloseConnectionAfterResponse = value > 1.0f;
|
||||||
|
return Task.FromResult(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ using System.IO;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DiscordBotCore.API;
|
using DiscordBotCore.API;
|
||||||
|
using DiscordBotCore.API.Endpoints;
|
||||||
|
using DiscordBotCore.API.Sockets;
|
||||||
using DiscordBotCore.Bot;
|
using DiscordBotCore.Bot;
|
||||||
using DiscordBotCore.Interfaces.Logger;
|
using DiscordBotCore.Interfaces.Logger;
|
||||||
using DiscordBotCore.Online;
|
using DiscordBotCore.Online;
|
||||||
@@ -46,7 +48,8 @@ namespace DiscordBotCore
|
|||||||
public InternalActionManager InternalActionManager { get; private set; } = null!;
|
public InternalActionManager InternalActionManager { get; private set; } = null!;
|
||||||
public PluginManager PluginManager { get; private set; } = null!;
|
public PluginManager PluginManager { get; private set; } = null!;
|
||||||
public ILogger Logger { get; private set; } = null!;
|
public ILogger Logger { get; private set; } = null!;
|
||||||
public ApiManager? ApiManager { get; private set; }
|
internal ApiManager? ApiManager { get; private set; }
|
||||||
|
internal SocketManager? SocketManager { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create the application. This method is used to initialize the application. Can not initialize multiple times.
|
/// Create the application. This method is used to initialize the application. Can not initialize multiple times.
|
||||||
@@ -113,9 +116,24 @@ namespace DiscordBotCore
|
|||||||
|
|
||||||
CurrentApplication.ApiManager = new ApiManager();
|
CurrentApplication.ApiManager = new ApiManager();
|
||||||
CurrentApplication.ApiManager.AddBaseEndpoints();
|
CurrentApplication.ApiManager.AddBaseEndpoints();
|
||||||
|
CurrentApplication.ApiManager.InitializeApi();
|
||||||
|
}
|
||||||
|
|
||||||
Thread apiThread = new Thread(() => _ = CurrentApplication.ApiManager.InitializeApi());
|
public static void InitializeThreadedSockets()
|
||||||
apiThread.Start();
|
{
|
||||||
|
if (CurrentApplication is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(CurrentApplication.SocketManager is not null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentApplication.SocketManager = new SocketManager(new ConnectionDetails("localhost", 5055));
|
||||||
|
CurrentApplication.SocketManager.RegisterBaseSockets();
|
||||||
|
CurrentApplication.SocketManager.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetResourceFullPath(string path)
|
public static string GetResourceFullPath(string path)
|
||||||
|
|||||||
7
DiscordBotCore/Interfaces/API/IConnectionDetails.cs
Normal file
7
DiscordBotCore/Interfaces/API/IConnectionDetails.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace DiscordBotCore.Interfaces.API;
|
||||||
|
|
||||||
|
public interface IConnectionDetails
|
||||||
|
{
|
||||||
|
public string Host { get; }
|
||||||
|
public int Port { get; }
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DiscordBotCore.API;
|
using DiscordBotCore.API.Endpoints;
|
||||||
using DiscordBotCore.Others;
|
|
||||||
|
|
||||||
namespace DiscordBotCore.Interfaces.API;
|
namespace DiscordBotCore.Interfaces.API;
|
||||||
|
|
||||||
|
|||||||
11
DiscordBotCore/Interfaces/API/ISocket.cs
Normal file
11
DiscordBotCore/Interfaces/API/ISocket.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Net.WebSockets;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using DiscordBotCore.API.Sockets;
|
||||||
|
|
||||||
|
namespace DiscordBotCore.Interfaces.API;
|
||||||
|
|
||||||
|
internal interface ISocket
|
||||||
|
{
|
||||||
|
public string Path { get; }
|
||||||
|
public Task<SocketResponse> HandleRequest(byte[] request, int count);
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<RadzenDataGridColumn Property="@nameof(PluginModel.Description)" Title="Version"/>
|
<RadzenDataGridColumn Property="@nameof(PluginModel.Description)" Title="Version"/>
|
||||||
<RadzenDataGridColumn Title="Download">
|
<RadzenDataGridColumn Title="Download">
|
||||||
<Template Context="item">
|
<Template Context="item">
|
||||||
<RadzenButton Click="() => OnPluginDownloadClick!(item.Name)" Text="Download"/>
|
<RadzenButton Click="() => OnPluginDownloadClick!(item.Name)" Text="Install"/>
|
||||||
</Template>
|
</Template>
|
||||||
</RadzenDataGridColumn>
|
</RadzenDataGridColumn>
|
||||||
</Columns>
|
</Columns>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
});
|
});
|
||||||
if(!response.Success)
|
if(!response.Success)
|
||||||
{
|
{
|
||||||
|
|
||||||
Console.WriteLine(response.Message);
|
Console.WriteLine(response.Message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user