Download plugin have progress status

This commit is contained in:
2024-12-25 15:05:20 +02:00
parent a754b0e5a9
commit 49fe637455
10 changed files with 146 additions and 57 deletions

View File

@@ -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 /// Some startup actions that can are executed when the console first starts. This actions are invoked externally at application launch
/// </summary> /// </summary>
private static readonly List<IStartupAction> StartupActions = [ private static readonly List<IStartupAction> StartupActions = [
new StartupAction("/purge_plugins", () => { new StartupAction("--clean-up", () => {
foreach (var plugin in Directory.GetFiles("./Data/Plugins", "*.dll", SearchOption.AllDirectories)) foreach (var plugin in Directory.GetFiles("./Data/Plugins", "*.dll", SearchOption.AllDirectories))
{ {
File.Delete(plugin); File.Delete(plugin);

View File

@@ -25,7 +25,6 @@ public class ApiManager
AddEndpoint(new PluginListEndpoint()); AddEndpoint(new PluginListEndpoint());
AddEndpoint(new PluginListInstalledEndpoint()); AddEndpoint(new PluginListInstalledEndpoint());
AddEndpoint(new PluginInstallEndpoint()); AddEndpoint(new PluginInstallEndpoint());
AddEndpoint(new PluginInstallGetProgressEndpoint());
AddEndpoint(new SettingsChangeEndpoint()); AddEndpoint(new SettingsChangeEndpoint());
AddEndpoint(new SettingsGetEndpoint()); AddEndpoint(new SettingsGetEndpoint());

View File

@@ -23,7 +23,7 @@ public class PluginInstallEndpoint : IEndpoint
return ApiResponse.Fail("Plugin not found."); return ApiResponse.Fail("Plugin not found.");
} }
await Application.CurrentApplication.PluginManager.InstallPluginWithNoProgress(pluginInfo); Application.CurrentApplication.PluginManager.InstallPluginWithNoProgress(pluginInfo);
return ApiResponse.Ok(); return ApiResponse.Ok();
} }
} }

View File

@@ -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<ApiResponse> 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<string, string>
{
{"progress", stringProgress},
{"pluginName", Application.CurrentApplication.PluginManager.InstallingPluginInformation.PluginName}
};
return ApiResponse.From(await JsonManager.ConvertToJsonString(response), true);
}
}

View File

@@ -31,8 +31,8 @@ internal class SocketResponse
return new SocketResponse(data, true, true, false); 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);
} }
} }

View File

@@ -6,15 +6,17 @@ namespace DiscordBotCore.API.Sockets.Sockets;
internal class PluginDownloadProgressSocket : ISocket internal class PluginDownloadProgressSocket : ISocket
{ {
private float value = 0.0f;
public string Path => "/plugin/download/progress"; public string Path => "/plugin/download/progress";
public Task<SocketResponse> HandleRequest(byte[] request, int count) public Task<SocketResponse> HandleRequest(byte[] request, int count)
{ {
value += 0.1f; if (!Application.CurrentApplication.PluginManager.InstallingPluginInformation.IsInstalling)
string pluginName = Encoding.UTF8.GetString(request, 0, count); {
Application.CurrentApplication.Logger.Log($"Received plugin download progress for {pluginName}."); return Task.FromResult(SocketResponse.Fail(true));
}
float value = Application.CurrentApplication.PluginManager.InstallingPluginInformation.InstallationProgress;
SocketResponse response = SocketResponse.From(Encoding.UTF8.GetBytes(value.ToString())); SocketResponse response = SocketResponse.From(Encoding.UTF8.GetBytes(value.ToString()));
response.CloseConnectionAfterResponse = value > 1.0f; response.CloseConnectionAfterResponse = false;
return Task.FromResult(response); return Task.FromResult(response);
} }
} }

View File

@@ -194,46 +194,73 @@ public sealed class PluginManager
{ {
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; 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; float currentProgress = 0f;
InstallingPluginInformation.IsInstalling = true; InstallingPluginInformation.IsInstalling = true;
IProgress<float> downloadProgress = new Progress<float>(f => InstallingPluginInformation.InstallationProgress = currentProgress + stepProgress * f); // Create a progress updater that maps the file's 0100 progress to its portion of the total progress
IProgress<float> downloadProgress = new Progress<float>(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, await ServerCom.DownloadFileAsync(pluginData.DownLoadLink,
$"{Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("PluginFolder")}/{pluginData.Name}.dll", $"{Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("PluginFolder")}/{pluginData.Name}.dll",
downloadProgress downloadProgress
); );
// Update cumulative progress after the main file
currentProgress += stepFraction;
// Download file dependencies if they exist
if (pluginData.HasFileDependencies) if (pluginData.HasFileDependencies)
{
foreach (var dependency in pluginData.Dependencies) 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); 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) if (pluginData.HasScriptDependencies)
{
foreach (var scriptDependency in pluginData.ScriptDependencies) foreach (var scriptDependency in pluginData.ScriptDependencies)
{ {
string console = OperatingSystem.IsWindows() ? "start cmd.exe" : "bash"; string console = OperatingSystem.IsWindows() ? "start cmd.exe" : "bash";
string arguments = OperatingSystem.IsWindows() ? $"/c {scriptDependency.ScriptContent}" : scriptDependency.ScriptContent; string arguments = OperatingSystem.IsWindows()
? $"/c {scriptDependency.ScriptContent}"
: scriptDependency.ScriptContent;
await ServerCom.RunConsoleCommand(console, arguments); await ServerCom.RunConsoleCommand(console, arguments);
} }
}
// Register the plugin in the database
PluginInfo pluginInfo = PluginInfo.FromOnlineInfo(pluginData); PluginInfo pluginInfo = PluginInfo.FromOnlineInfo(pluginData);
await AppendPluginToDatabase(pluginInfo); await AppendPluginToDatabase(pluginInfo);
InstallingPluginInformation.IsInstalling = false; InstallingPluginInformation.IsInstalling = false; // Mark installation as complete
} }
public async Task InstallPluginWithProgressBar(PluginOnlineInfo pluginData, IProgress<float>? installProgress) public async Task InstallPluginWithProgressBar(PluginOnlineInfo pluginData, IProgress<float>? installProgress)
{ {
installProgress?.Report(0f); installProgress?.Report(0f);

View File

@@ -0,0 +1,32 @@
@using DiscordBotWebUI.ServerCommunication.Sockets
@inject DialogService DialogService
<RadzenProgressBar Value="progressValue" ShowValue="false"/>
@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();
}
}

View File

@@ -2,6 +2,7 @@
@using DiscordBotWebUI.Components.CustomTags @using DiscordBotWebUI.Components.CustomTags
@using DiscordBotWebUI.Models @using DiscordBotWebUI.Models
@using DiscordBotWebUI.ServerCommunication @using DiscordBotWebUI.ServerCommunication
@inject DialogService DialogService
<Marketplace PluginModels="_PluginModels" OnPluginDownloadClick="OnModelSelected"/> <Marketplace PluginModels="_PluginModels" OnPluginDownloadClick="OnModelSelected"/>
@@ -20,12 +21,22 @@
}); });
if(!response.Success) if(!response.Success)
{ {
Console.WriteLine(response.Message); Console.WriteLine(response.Message);
return; return;
} }
Console.WriteLine("Plugin installed"); await DialogService.OpenAsync<ProgressDialog>($"Installing {itemName}", new Dictionary<string, object>()
{
{"ProgressUrl", "/plugin/download/progress"},
{"FirstMessage", itemName}
}, new DialogOptions()
{
Draggable = false,
CloseDialogOnEsc = false,
CloseDialogOnOverlayClick = false,
ShowClose = false
});
} }
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()

View File

@@ -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<string> 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<byte>(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<byte>(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);
}
}
}
}