Updated plugin installation and plugin loading
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ public sealed class Logger : ILogger
|
||||
{
|
||||
string fileName = _logFileStream.Name;
|
||||
|
||||
_logFileStream.Flush();
|
||||
//_logFileStream.Flush();
|
||||
_logFileStream.Close();
|
||||
|
||||
string[] logs = File.ReadAllLines(fileName);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() }
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user