Updated plugin installation and plugin loading

This commit is contained in:
2025-04-06 20:47:00 +03:00
parent 87c889266b
commit 4ab8438a7c
14 changed files with 122 additions and 67 deletions

View File

@@ -42,20 +42,20 @@ public class Configuration : ConfigurationBase
return value;
}
public override async void LoadFromFile()
public override void LoadFromFile()
{
if (!File.Exists(_DiskLocation))
{
await SaveToFile();
SaveToFile().Wait();
return;
}
string jsonContent = await File.ReadAllTextAsync(_DiskLocation);
string jsonContent = File.ReadAllText(_DiskLocation);
var jObject = JsonConvert.DeserializeObject<JObject>(jsonContent);
if (jObject is null)
{
await SaveToFile();
SaveToFile().Wait();
return;
}

View File

@@ -91,7 +91,7 @@ public sealed class Logger : ILogger
{
string fileName = _logFileStream.Name;
_logFileStream.Flush();
//_logFileStream.Flush();
_logFileStream.Close();
string[] logs = File.ReadAllLines(fileName);

View File

@@ -19,5 +19,5 @@ public interface IDbEvent
/// The method that is invoked when the event is loaded into memory
/// </summary>
/// <param name="args">The arguments for the start method</param>
Task Start(IDbEventExecutingArgument args);
void Start(IDbEventExecutingArgument args);
}

View File

@@ -116,28 +116,48 @@ public sealed class PluginLoader : IPluginLoader
);
}
private bool TryStartEvent(IDbEvent? dbEvent)
private bool TryStartEvent(IDbEvent dbEvent)
{
try
string? botPrefix = _Configuration.Get<string>("prefix");
if (string.IsNullOrEmpty(botPrefix))
{
if (dbEvent is null)
{
throw new ArgumentNullException(nameof(dbEvent));
}
IDbEventExecutingArgument args = new DbEventExecutingArgument(
_Logger,
_discordClient,
_Configuration.Get<string>("prefix"),
new DirectoryInfo(Path.Combine(_Configuration.Get<string>("ResourcesPath"), dbEvent.Name)));
dbEvent.Start(args);
return true;
}
catch (Exception e)
{
_Logger.Log($"Error starting event {dbEvent.Name}: {e.Message}", typeof(PluginLoader), LogType.Error);
_Logger.LogException(e, typeof(PluginLoader));
_Logger.Log("Bot prefix is not set. Please set the bot prefix in the configuration.", this, LogType.Error);
return false;
}
if (_discordClient is null)
{
_Logger.Log("Discord client is not set. Please set the discord client before starting events.", this, LogType.Error);
return false;
}
string? resourcesFolder = _Configuration.Get<string>("ResourcesFolder");
if (string.IsNullOrEmpty(resourcesFolder))
{
_Logger.Log("Resources folder is not set. Please set the resources folder in the configuration.", this, LogType.Error);
return false;
}
if (!Directory.Exists(resourcesFolder))
{
_Logger.Log("Resources folder does not exist. Please create the resources folder.", this, LogType.Error);
return false;
}
string? eventConfigDirectory = Path.Combine(resourcesFolder, dbEvent.GetType().Assembly.GetName().Name);
Directory.CreateDirectory(eventConfigDirectory);
IDbEventExecutingArgument args = new DbEventExecutingArgument(
_Logger,
_discordClient,
botPrefix,
new DirectoryInfo(eventConfigDirectory));
dbEvent.Start(args);
_Logger.Log("Event started: " + dbEvent.Name, this);
return true;
}
private async Task<Result> TryStartSlashCommand(IDbSlashCommand? dbSlashCommand)

View File

@@ -45,7 +45,7 @@ public class PluginRepository : IPluginRepository
public async Task<OnlinePlugin?> GetPluginById(int pluginId)
{
string url = CreateUrlWithQueryParams(_pluginRepositoryConfiguration.PluginRepositoryLocation,
"get-plugin", new Dictionary<string, string>
"get-by-id", new Dictionary<string, string>
{
{ "pluginId", pluginId.ToString() },
{ "includeNotApproved", "false" }
@@ -66,7 +66,7 @@ public class PluginRepository : IPluginRepository
public async Task<OnlinePlugin?> GetPluginByName(string pluginName, int operatingSystem, bool includeNotApproved)
{
string url = CreateUrlWithQueryParams(_pluginRepositoryConfiguration.PluginRepositoryLocation,
"get-plugin-by-name", new Dictionary<string, string>
"get-by-name", new Dictionary<string, string>
{
{ "pluginName", pluginName },
{ "operatingSystem", operatingSystem.ToString() },
@@ -89,7 +89,7 @@ public class PluginRepository : IPluginRepository
public async Task<List<OnlineDependencyInfo>> GetDependenciesForPlugin(int pluginId)
{
string url = CreateUrlWithQueryParams(_pluginRepositoryConfiguration.DependenciesRepositoryLocation,
"get-dependencies-for-plugin", new Dictionary<string, string>
"get-by-plugin-id", new Dictionary<string, string>
{
{ "pluginId", pluginId.ToString() }
});

View File

@@ -6,6 +6,7 @@ public interface IPluginManager
{
Task<List<OnlinePlugin>> GetPluginsList();
Task<OnlinePlugin?> GetPluginDataByName(string pluginName);
Task<OnlinePlugin?> GetPluginDataById(int pluginId);
Task AppendPluginToDatabase(LocalPlugin pluginData);
Task<List<LocalPlugin>> GetInstalledPlugins();
Task<bool> IsPluginInstalled(string pluginName);
@@ -14,7 +15,7 @@ public interface IPluginManager
Task<string?> GetDependencyLocation(string dependencyName);
Task<string?> GetDependencyLocation(string dependencyName, string pluginName);
string GenerateDependencyRelativePath(string pluginName, string dependencyPath);
Task InstallPlugin(OnlinePlugin plugin, IProgress<InstallationProgressIndicator> progress);
Task InstallPlugin(OnlinePlugin plugin, IProgress<float> progress);
Task<Tuple<Dictionary<string, string>, List<OnlineDependencyInfo>>> GatherInstallDataForPlugin(OnlinePlugin plugin);
Task SetEnabledStatus(string pluginName, bool status);
}

View File

@@ -1,16 +0,0 @@
namespace DiscordBotCore.PluginManagement.Models;
public class InstallationProgressIndicator
{
private readonly Dictionary<string, float> _DownloadProgress;
public InstallationProgressIndicator()
{
_DownloadProgress = new Dictionary<string, float>();
}
public void SetProgress(string fileName, float progress)
{
_DownloadProgress[fileName] = progress;
}
}

View File

@@ -50,6 +50,18 @@ public sealed class PluginManager : IPluginManager
return plugin;
}
public async Task<OnlinePlugin?> GetPluginDataById(int pluginId)
{
var plugin = await _PluginRepository.GetPluginById(pluginId);
if (plugin is null)
{
_Logger.Log($"Plugin {pluginId} not found in the repository.", this, LogType.Warning);
return null;
}
return plugin;
}
private async Task RemovePluginFromDatabase(string pluginName)
{
string? pluginDatabaseFile = _Configuration.Get<string>("PluginDatabase");
@@ -72,8 +84,9 @@ public sealed class PluginManager : IPluginManager
{
throw new Exception("Plugin database file not found");
}
List<LocalPlugin> installedPlugins = await GetInstalledPlugins();
List<LocalPlugin> installedPlugins = await JsonManager.ConvertFromJson<List<LocalPlugin>>(await File.ReadAllTextAsync(pluginDatabaseFile));
foreach (var dependency in pluginData.ListOfExecutableDependencies)
{
pluginData.ListOfExecutableDependencies[dependency.Key] = dependency.Value;
@@ -194,7 +207,7 @@ public sealed class PluginManager : IPluginManager
return relative;
}
public async Task InstallPlugin(OnlinePlugin plugin, IProgress<InstallationProgressIndicator> progress)
public async Task InstallPlugin(OnlinePlugin plugin, IProgress<float> progress)
{
List<OnlineDependencyInfo> dependencies = await _PluginRepository.GetDependenciesForPlugin(plugin.Id);
string? pluginsFolder = _Configuration.Get<string>("PluginFolder");
@@ -205,13 +218,7 @@ public sealed class PluginManager : IPluginManager
string downloadLocation = $"{pluginsFolder}/{plugin.Name}.dll";
InstallationProgressIndicator installationProgressIndicator = new InstallationProgressIndicator();
IProgress<float> downloadProgress = new Progress<float>(fileProgress =>
{
installationProgressIndicator.SetProgress(plugin.Name, fileProgress);
progress.Report(installationProgressIndicator);
});
IProgress<float> downloadProgress = new Progress<float>(progress.Report);
FileDownloader fileDownloader = new FileDownloader(plugin.DownloadLink, downloadLocation);
await fileDownloader.DownloadFile(downloadProgress.Report);
@@ -221,16 +228,14 @@ public sealed class PluginManager : IPluginManager
foreach (var dependency in dependencies)
{
string dependencyLocation = GenerateDependencyRelativePath(plugin.Name, dependency.DownloadLocation);
Action<float> dependencyProgress = new Action<float>(fileProgress =>
{
installationProgressIndicator.SetProgress(dependency.DependencyName, fileProgress);
progress.Report(installationProgressIndicator);
});
executor.AddTask(dependency.DownloadLink, dependencyLocation, dependencyProgress);
executor.AddTask(dependency.DownloadLink, dependencyLocation, progress.Report);
}
await executor.ExecuteAllTasks();
LocalPlugin localPlugin = LocalPlugin.FromOnlineInfo(plugin, dependencies, downloadLocation);
await AppendPluginToDatabase(localPlugin);
}
public async Task<Tuple<Dictionary<string, string>, List<OnlineDependencyInfo>>> GatherInstallDataForPlugin(OnlinePlugin plugin)

View File

@@ -15,9 +15,9 @@ internal class LevelEvent : IDbEvent
public string Name => "Leveling System Event Handler";
public string Description => "The Leveling System Event Handler";
public async Task Start(IDbEventExecutingArgument args)
public async void Start(IDbEventExecutingArgument args)
{
args.Logger.Log("Starting Leveling System Event Handler", this);
Directory.CreateDirectory(DataFolder);
await Task.Delay(200);
Database = new SqlDatabase(DataFolder + "Users.db");

View File

@@ -65,7 +65,7 @@ public class HomeController : Controller
{
_logger.Log("Loading plugins", this);
await _pluginLoader.LoadPlugins();
_logger.Log("Plugins loaded", this);
//_logger.Log("Plugins loaded", this);
return RedirectToAction("Index");
}
}

View File

@@ -1,4 +1,5 @@
using DiscordBotCore.PluginManagement;
using DiscordBotCore.PluginManagement.Models;
using Microsoft.AspNetCore.Mvc;
using WebUI.Models;
using ILogger = DiscordBotCore.Logging.ILogger;
@@ -32,6 +33,7 @@ public class PluginsController : Controller
pluginViewModel.Author = plugin.Author;
pluginViewModel.Version = plugin.Version;
pluginViewModel.DownloadUrl = plugin.DownloadLink;
pluginViewModel.Id = plugin.Id;
pluginViewModels.Add(pluginViewModel);
}
@@ -66,4 +68,22 @@ public class PluginsController : Controller
//TODO: Implement delete plugin
return RedirectToAction("InstalledPlugins");
}
[HttpPost]
public async Task<IActionResult> InstallPlugin(int pluginId)
{
var pluginData = await _pluginManager.GetPluginDataById(pluginId);
if (pluginData is null)
{
_logger.Log($"Plugin with ID {pluginId} not found", this);
return RedirectToAction("OnlinePlugins");
}
IProgress<float> progress = new Progress<float>(f => _logger.Log($"Installing: {f}"));
await _pluginManager.InstallPlugin(pluginData, progress);
_logger.Log($"Plugin {pluginData.Name} installed", this);
return RedirectToAction("OnlinePlugins");
}
}

View File

@@ -2,10 +2,10 @@ namespace WebUI.Models;
public class OnlinePluginViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Author { get; set; }
public string Version { get; set; }
public string DownloadUrl { get; set; }
}

View File

@@ -73,6 +73,7 @@ var builder = WebApplication.CreateBuilder(args);
string defaultLogFormat = "{ThrowTime} {SenderName} {Message}";
string defaultLogFolder = "./Data/Logs";
string defaultResourcesFolder = "./Data/Resources";
string defaultConfigFile = "./Data/Resources/config.json";
string defaultPluginFolder = "./Data/Plugins";
string defaultPluginDatabaseFile = "./Data/Resources/plugins.json";
@@ -135,7 +136,12 @@ builder.Services.AddSingleton<IPluginManager>(sp =>
ILogger logger = sp.GetRequiredService<ILogger>();
IConfiguration configuration = sp.GetRequiredService<IConfiguration>();
Directory.CreateDirectory(configuration.Get<string>("PluginFolder", defaultPluginFolder));
string pluginFolder = configuration.Get<string>("PluginFolder", defaultPluginFolder);
Directory.CreateDirectory(pluginFolder);
string resourcesFolder = configuration.Get<string>("ResourcesFolder", defaultResourcesFolder);
Directory.CreateDirectory(resourcesFolder);
string pluginDatabaseFile = configuration.Get<string>("PluginDatabase", defaultPluginDatabaseFile);
Directory.CreateDirectory(new FileInfo(pluginDatabaseFile).DirectoryName);
@@ -159,8 +165,6 @@ builder.Services.AddSingleton<IDiscordBotApplication>(sp =>
return new DiscordBotApplication(logger, configuration, pluginLoader);
});
var app = builder.Build();
// Configure the HTTP request pipeline.
@@ -182,4 +186,23 @@ app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
// Force eager creation of all required services
using (var scope = app.Services.CreateScope())
{
var provider = scope.ServiceProvider;
// Manually resolve all your singletons here
provider.GetRequiredService<ILogger>();
IConfiguration config = provider.GetRequiredService<IConfiguration>();
provider.GetRequiredService<IPluginRepositoryConfiguration>();
provider.GetRequiredService<IPluginRepository>();
provider.GetRequiredService<IPluginManager>();
provider.GetRequiredService<IPluginLoader>();
provider.GetRequiredService<IDiscordBotApplication>();
// Optional: Log that all services were initialized
provider.GetRequiredService<ILogger>().Log("All core services have been initialized at startup.");
}
app.Run();

View File

@@ -25,7 +25,9 @@
<td>@plugin.Author</td>
<td>@plugin.Version</td>
<td>
<a href="@plugin.DownloadUrl" class="btn btn-primary" target="_blank">Download</a>
<form method="post" asp-action="InstallPlugin" asp-route-pluginId="@plugin.Id">
<button type="submit" class="btn btn-primary">Download</button>
</form>
</td>
</tr>
}