From 49fe637455a7d40635f9a6f431384408f4f4a539 Mon Sep 17 00:00:00 2001 From: Andrei Tudor Date: Wed, 25 Dec 2024 15:05:20 +0200 Subject: [PATCH] Download plugin have progress status --- DiscordBot/Entry.cs | 2 +- DiscordBotCore/API/Endpoints/ApiManager.cs | 1 - .../PluginManagement/PluginInstallEndpoint.cs | 2 +- .../PluginInstallGetProgressEndpoint.cs | 29 --------- DiscordBotCore/API/Sockets/SocketResponse.cs | 4 +- .../Sockets/PluginDownloadProgressSocket.cs | 12 ++-- DiscordBotCore/Online/PluginManager.cs | 59 ++++++++++++++----- .../CustomTags/ProgressDialog.razor | 32 ++++++++++ .../Pages/SidebarPages/PluginMarket.razor | 15 ++++- .../Sockets/WebSocketClientService.cs | 47 +++++++++++++++ 10 files changed, 146 insertions(+), 57 deletions(-) delete mode 100644 DiscordBotCore/API/Endpoints/PluginManagement/PluginInstallGetProgressEndpoint.cs create mode 100644 DiscordBotWebUI/Components/CustomTags/ProgressDialog.razor create mode 100644 DiscordBotWebUI/ServerCommunication/Sockets/WebSocketClientService.cs diff --git a/DiscordBot/Entry.cs b/DiscordBot/Entry.cs index 069931c..f3da4aa 100644 --- a/DiscordBot/Entry.cs +++ b/DiscordBot/Entry.cs @@ -12,7 +12,7 @@ public static class Entry /// Some startup actions that can are executed when the console first starts. This actions are invoked externally at application launch /// private static readonly List StartupActions = [ - new StartupAction("/purge_plugins", () => { + new StartupAction("--clean-up", () => { foreach (var plugin in Directory.GetFiles("./Data/Plugins", "*.dll", SearchOption.AllDirectories)) { File.Delete(plugin); diff --git a/DiscordBotCore/API/Endpoints/ApiManager.cs b/DiscordBotCore/API/Endpoints/ApiManager.cs index cb942c8..1d36165 100644 --- a/DiscordBotCore/API/Endpoints/ApiManager.cs +++ b/DiscordBotCore/API/Endpoints/ApiManager.cs @@ -25,7 +25,6 @@ public class ApiManager AddEndpoint(new PluginListEndpoint()); AddEndpoint(new PluginListInstalledEndpoint()); AddEndpoint(new PluginInstallEndpoint()); - AddEndpoint(new PluginInstallGetProgressEndpoint()); AddEndpoint(new SettingsChangeEndpoint()); AddEndpoint(new SettingsGetEndpoint()); diff --git a/DiscordBotCore/API/Endpoints/PluginManagement/PluginInstallEndpoint.cs b/DiscordBotCore/API/Endpoints/PluginManagement/PluginInstallEndpoint.cs index 5ea1ac9..8fce27e 100644 --- a/DiscordBotCore/API/Endpoints/PluginManagement/PluginInstallEndpoint.cs +++ b/DiscordBotCore/API/Endpoints/PluginManagement/PluginInstallEndpoint.cs @@ -23,7 +23,7 @@ public class PluginInstallEndpoint : IEndpoint return ApiResponse.Fail("Plugin not found."); } - await Application.CurrentApplication.PluginManager.InstallPluginWithNoProgress(pluginInfo); + Application.CurrentApplication.PluginManager.InstallPluginWithNoProgress(pluginInfo); return ApiResponse.Ok(); } } diff --git a/DiscordBotCore/API/Endpoints/PluginManagement/PluginInstallGetProgressEndpoint.cs b/DiscordBotCore/API/Endpoints/PluginManagement/PluginInstallGetProgressEndpoint.cs deleted file mode 100644 index 31e020a..0000000 --- a/DiscordBotCore/API/Endpoints/PluginManagement/PluginInstallGetProgressEndpoint.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using System.Globalization; -using System.Threading.Tasks; -using DiscordBotCore.Interfaces.API; -using DiscordBotCore.Others; - -namespace DiscordBotCore.API.Endpoints.PluginManagement; - -public class PluginInstallGetProgressEndpoint : IEndpoint -{ - public string Path => "/api/plugin/install/progress"; - public EndpointType HttpMethod => EndpointType.Get; - public async Task HandleRequest(string? jsonRequest) - { - if (!Application.CurrentApplication.PluginManager.InstallingPluginInformation.IsInstalling) - { - return ApiResponse.Fail("No plugin is currently being installed."); - } - - var progress = Application.CurrentApplication.PluginManager.InstallingPluginInformation.InstallationProgress; - string stringProgress = progress.ToString(CultureInfo.InvariantCulture); - var response = new Dictionary - { - {"progress", stringProgress}, - {"pluginName", Application.CurrentApplication.PluginManager.InstallingPluginInformation.PluginName} - }; - return ApiResponse.From(await JsonManager.ConvertToJsonString(response), true); - } -} diff --git a/DiscordBotCore/API/Sockets/SocketResponse.cs b/DiscordBotCore/API/Sockets/SocketResponse.cs index 9a31682..718d6f3 100644 --- a/DiscordBotCore/API/Sockets/SocketResponse.cs +++ b/DiscordBotCore/API/Sockets/SocketResponse.cs @@ -31,8 +31,8 @@ internal class SocketResponse return new SocketResponse(data, true, true, false); } - internal static SocketResponse Fail() + internal static SocketResponse Fail(bool closeConnection) { - return new SocketResponse(new byte[0], true, false, false); + return new SocketResponse(new byte[0], true, false, closeConnection); } } diff --git a/DiscordBotCore/API/Sockets/Sockets/PluginDownloadProgressSocket.cs b/DiscordBotCore/API/Sockets/Sockets/PluginDownloadProgressSocket.cs index e1ca96a..12edc6d 100644 --- a/DiscordBotCore/API/Sockets/Sockets/PluginDownloadProgressSocket.cs +++ b/DiscordBotCore/API/Sockets/Sockets/PluginDownloadProgressSocket.cs @@ -6,15 +6,17 @@ namespace DiscordBotCore.API.Sockets.Sockets; internal class PluginDownloadProgressSocket : ISocket { - private float value = 0.0f; public string Path => "/plugin/download/progress"; public Task 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}."); + if (!Application.CurrentApplication.PluginManager.InstallingPluginInformation.IsInstalling) + { + return Task.FromResult(SocketResponse.Fail(true)); + } + + float value = Application.CurrentApplication.PluginManager.InstallingPluginInformation.InstallationProgress; SocketResponse response = SocketResponse.From(Encoding.UTF8.GetBytes(value.ToString())); - response.CloseConnectionAfterResponse = value > 1.0f; + response.CloseConnectionAfterResponse = false; return Task.FromResult(response); } } diff --git a/DiscordBotCore/Online/PluginManager.cs b/DiscordBotCore/Online/PluginManager.cs index cefdbd0..324fef6 100644 --- a/DiscordBotCore/Online/PluginManager.cs +++ b/DiscordBotCore/Online/PluginManager.cs @@ -192,48 +192,75 @@ public sealed class PluginManager public async Task InstallPluginWithNoProgress(PluginOnlineInfo pluginData) { - InstallingPluginInformation = new InstallingPluginInformation() {PluginName = pluginData.Name}; - + InstallingPluginInformation = new InstallingPluginInformation() { PluginName = pluginData.Name }; + + // Calculate the total number of steps: main file + dependencies int totalSteps = pluginData.HasFileDependencies ? pluginData.Dependencies.Count + 1 : 1; - float stepProgress = 1f / totalSteps; + // Each step contributes this percentage to the total progress + float stepFraction = 100f / totalSteps; + + // Tracks the current cumulative progress in percentage float currentProgress = 0f; - + InstallingPluginInformation.IsInstalling = true; - - IProgress downloadProgress = new Progress(f => InstallingPluginInformation.InstallationProgress = currentProgress + stepProgress * f); - + + // Create a progress updater that maps the file's 0–100 progress to its portion of the total progress + IProgress downloadProgress = new Progress(fileProgress => + { + // Map the file progress (0-100) to the total progress + InstallingPluginInformation.InstallationProgress = currentProgress + (fileProgress / 100f) * stepFraction; + }); + + // Download the main plugin file and map its progress await ServerCom.DownloadFileAsync(pluginData.DownLoadLink, $"{Application.CurrentApplication.ApplicationEnvironmentVariables.Get("PluginFolder")}/{pluginData.Name}.dll", downloadProgress ); - + + // Update cumulative progress after the main file + currentProgress += stepFraction; + + // Download file dependencies if they exist if (pluginData.HasFileDependencies) + { foreach (var dependency in pluginData.Dependencies) { - string dependencyLocation = GenerateDependencyRelativePath(pluginData.Name, dependency.DownloadLocation); + string dependencyLocation = + GenerateDependencyRelativePath(pluginData.Name, dependency.DownloadLocation); + + // Download dependency and map its progress await ServerCom.DownloadFileAsync(dependency.DownloadLink, dependencyLocation, downloadProgress); - currentProgress += stepProgress; + // Update cumulative progress after each dependency + currentProgress += stepFraction; } + } + // Run script dependencies if any (doesn't affect download progress percentage) if (pluginData.HasScriptDependencies) + { foreach (var scriptDependency in pluginData.ScriptDependencies) { - - string console = OperatingSystem.IsWindows() ? "start cmd.exe" : "bash"; - string arguments = OperatingSystem.IsWindows() ? $"/c {scriptDependency.ScriptContent}" : scriptDependency.ScriptContent; + string console = OperatingSystem.IsWindows() ? "start cmd.exe" : "bash"; + string arguments = OperatingSystem.IsWindows() + ? $"/c {scriptDependency.ScriptContent}" + : scriptDependency.ScriptContent; await ServerCom.RunConsoleCommand(console, arguments); } + } + // Register the plugin in the database PluginInfo pluginInfo = PluginInfo.FromOnlineInfo(pluginData); - await AppendPluginToDatabase(pluginInfo); - InstallingPluginInformation.IsInstalling = false; + InstallingPluginInformation.IsInstalling = false; // Mark installation as complete } - + + + + public async Task InstallPluginWithProgressBar(PluginOnlineInfo pluginData, IProgress? installProgress) { installProgress?.Report(0f); diff --git a/DiscordBotWebUI/Components/CustomTags/ProgressDialog.razor b/DiscordBotWebUI/Components/CustomTags/ProgressDialog.razor new file mode 100644 index 0000000..17bd257 --- /dev/null +++ b/DiscordBotWebUI/Components/CustomTags/ProgressDialog.razor @@ -0,0 +1,32 @@ +@using DiscordBotWebUI.ServerCommunication.Sockets +@inject DialogService DialogService + + + +@code { + private float progressValue = 0f; + + [Parameter] + public string ProgressUrl { get; set; } + + [Parameter] + public string FirstMessage { get; set; } + + protected override async Task OnInitializedAsync() + { + + WebSocketClientService service = new WebSocketClientService(); + await service.ConnectAsync(ProgressUrl); + await Task.Delay(1000); + await service.SendMessageAsync(FirstMessage); + service.OnMessageReceived += (msg) => + { + progressValue = float.Parse(msg); + StateHasChanged(); + }; + await service.ReceiveMessages(); + await Task.Delay(500); + DialogService.Close(); + } + +} diff --git a/DiscordBotWebUI/Components/Pages/SidebarPages/PluginMarket.razor b/DiscordBotWebUI/Components/Pages/SidebarPages/PluginMarket.razor index 9a33941..0adab7e 100644 --- a/DiscordBotWebUI/Components/Pages/SidebarPages/PluginMarket.razor +++ b/DiscordBotWebUI/Components/Pages/SidebarPages/PluginMarket.razor @@ -2,6 +2,7 @@ @using DiscordBotWebUI.Components.CustomTags @using DiscordBotWebUI.Models @using DiscordBotWebUI.ServerCommunication +@inject DialogService DialogService @@ -20,12 +21,22 @@ }); if(!response.Success) { - Console.WriteLine(response.Message); return; } - Console.WriteLine("Plugin installed"); + await DialogService.OpenAsync($"Installing {itemName}", new Dictionary() + { + {"ProgressUrl", "/plugin/download/progress"}, + {"FirstMessage", itemName} + }, new DialogOptions() + { + Draggable = false, + CloseDialogOnEsc = false, + CloseDialogOnOverlayClick = false, + ShowClose = false + }); + } protected override async Task OnInitializedAsync() diff --git a/DiscordBotWebUI/ServerCommunication/Sockets/WebSocketClientService.cs b/DiscordBotWebUI/ServerCommunication/Sockets/WebSocketClientService.cs new file mode 100644 index 0000000..40fa75b --- /dev/null +++ b/DiscordBotWebUI/ServerCommunication/Sockets/WebSocketClientService.cs @@ -0,0 +1,47 @@ +using System; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace DiscordBotWebUI.ServerCommunication.Sockets; + +public class WebSocketClientService +{ + private ClientWebSocket _webSocket; + public event Action OnMessageReceived; + + private readonly string _BaseUrl = "ws://localhost:5055"; + + public async Task ConnectAsync(string uri) + { + _webSocket = new ClientWebSocket(); + await _webSocket.ConnectAsync(new Uri(_BaseUrl + uri), CancellationToken.None); + Console.WriteLine("Connected to WebSocket server."); + } + + public async Task SendMessageAsync(string message) + { + var messageBytes = Encoding.UTF8.GetBytes(message); + await _webSocket.SendAsync(new ArraySegment(messageBytes), WebSocketMessageType.Text, true, CancellationToken.None); + } + + public async Task ReceiveMessages() + { + byte[] buffer = new byte[102400]; + while (_webSocket.State == WebSocketState.Open) + { + var result = await _webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + if (result.MessageType == WebSocketMessageType.Close) + { + await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None); + Console.WriteLine("WebSocket connection closed."); + } + else + { + string receivedMessage = Encoding.UTF8.GetString(buffer, 0, result.Count); + OnMessageReceived?.Invoke(receivedMessage); + } + } + } +}