Actions are now loaded together with all plugins. Called the LoadPlugins at startup

This commit is contained in:
2024-06-08 19:17:15 +03:00
parent 9a8ddb5388
commit d9d5c05313
14 changed files with 148 additions and 236 deletions
+2 -2
View File
@@ -36,7 +36,7 @@ namespace DiscordBotCore
public InternalActionManager InternalActionManager { get; private set; }
public PluginManager PluginManager { get; private set; }
public Logger Logger { get; private set; }
public Bot.Boot DiscordBotClient { get; internal set; }
public Bot.App DiscordBotClient { get; internal set; }
public static async Task CreateApplication()
{
@@ -78,7 +78,7 @@ namespace DiscordBotCore
await CurrentApplication.PluginManager.UninstallMarkedPlugins();
await CurrentApplication.PluginManager.CheckForUpdates();
CurrentApplication.InternalActionManager = new InternalActionManager(CurrentApplication.ApplicationEnvironmentVariables["PluginFolder"], "dll");
CurrentApplication.InternalActionManager = new InternalActionManager();
await CurrentApplication.InternalActionManager.Initialize();
}
@@ -9,7 +9,7 @@ using DiscordBotCore.Others;
namespace DiscordBotCore.Bot;
public class Boot
public class App
{
/// <summary>
/// The bot prefix
@@ -41,7 +41,7 @@ public class Boot
/// </summary>
/// <param name="botToken">The bot token</param>
/// <param name="botPrefix">The bot prefix</param>
public Boot(string botToken, string botPrefix)
public App(string botToken, string botPrefix)
{
this.BotPrefix = botPrefix;
this.BotToken = botToken;
-72
View File
@@ -1,72 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using DiscordBotCore.Interfaces;
using DiscordBotCore.Others;
namespace DiscordBotCore.Loaders;
public class ActionsLoader
{
public delegate void ActionLoaded(string name, string typeName, bool success, Exception? e = null);
private readonly string _actionExtension;
private readonly string _actionFolder;
public ActionsLoader(string path, string extension)
{
_actionFolder = path;
_actionExtension = extension;
}
public event ActionLoaded? ActionLoadedEvent;
public async Task<List<ICommandAction>?> Load()
{
Directory.CreateDirectory(_actionFolder);
var files = Directory.GetFiles(_actionFolder, $"*.{_actionExtension}", SearchOption.AllDirectories);
var actions = new List<ICommandAction>();
foreach (var file in files)
try
{
Assembly.LoadFrom(file);
}
catch (Exception e)
{
ActionLoadedEvent?.Invoke(file, "", false, e);
}
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => typeof(ICommandAction).IsAssignableFrom(p) && !p.IsInterface);
foreach (var type in types)
try
{
var action = (ICommandAction)Activator.CreateInstance(type);
if (action.ActionName == null)
{
ActionLoadedEvent?.Invoke(action.ActionName, type.Name, false);
continue;
}
if (action.RunType != InternalActionRunType.ON_CALL)
action.ExecuteStartup();
ActionLoadedEvent?.Invoke(action.ActionName, type.Name, true);
actions.Add(action);
}
catch (Exception e)
{
ActionLoadedEvent?.Invoke(type.Name, type.Name, false, e);
}
return actions;
}
}
+2 -15
View File
@@ -10,8 +10,6 @@ namespace DiscordBotCore.Loaders;
internal class Loader
{
private readonly string _SearchPath;
private readonly string _FileExtension;
internal delegate void FileLoadedHandler(FileLoaderResult result);
@@ -20,24 +18,11 @@ internal class Loader
internal event FileLoadedHandler? OnFileLoadedException;
internal event PluginLoadedHandler? OnPluginLoaded;
internal Loader(string searchPath, string fileExtension)
{
_SearchPath = searchPath;
_FileExtension = fileExtension;
}
internal async Task Load()
{
if (!Directory.Exists(_SearchPath))
{
Directory.CreateDirectory(_SearchPath);
return;
}
var installedPlugins = await Application.CurrentApplication.PluginManager.GetInstalledPlugins();
var files = installedPlugins.Select(plugin => plugin.FilePath).ToArray();
//var files = Directory.GetFiles(_SearchPath, $"*.{_FileExtension}", SearchOption.TopDirectoryOnly);
foreach (var file in files)
{
try
@@ -53,6 +38,7 @@ internal class Loader
await LoadEverythingOfType<DBEvent>();
await LoadEverythingOfType<DBCommand>();
await LoadEverythingOfType<DBSlashCommand>();
await LoadEverythingOfType<ICommandAction>();
}
private async Task LoadEverythingOfType<T>()
@@ -77,6 +63,7 @@ internal class Loader
DBEvent => PluginType.EVENT,
DBCommand => PluginType.COMMAND,
DBSlashCommand => PluginType.SLASH_COMMAND,
ICommandAction => PluginType.ACTION,
_ => PluginType.UNKNOWN
};
+18 -3
View File
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Discord.WebSocket;
using DiscordBotCore.Interfaces;
@@ -17,13 +19,17 @@ public class PluginLoader
public delegate void SlashCommandLoaded(PluginLoadResultData resultData);
public delegate void ActionLoaded(PluginLoadResultData resultData);
public CommandLoaded? OnCommandLoaded;
public EventLoaded? OnEventLoaded;
public SlashCommandLoaded? OnSlashCommandLoaded;
public ActionLoaded? OnActionLoaded;
public static List<DBCommand> Commands { get; private set; } = new List<DBCommand>();
public static List<DBEvent> Events { get; private set; } = new List<DBEvent>();
public static List<DBSlashCommand> SlashCommands { get; private set; } = new List<DBSlashCommand>();
public static List<ICommandAction> Actions { get; private set; } = new List<ICommandAction>();
public PluginLoader(DiscordSocketClient discordSocketClient)
{
@@ -34,9 +40,7 @@ public class PluginLoader
{
Application.CurrentApplication.Logger.Log("Loading plugins...", this);
var loader = new Loader(Application.CurrentApplication.ApplicationEnvironmentVariables["PluginFolder"], "dll");
//await this.ResetSlashCommands();
var loader = new Loader();
loader.OnFileLoadedException += FileLoadedException;
loader.OnPluginLoaded += OnPluginLoaded;
@@ -53,6 +57,17 @@ public class PluginLoader
{
switch (result.PluginType)
{
case PluginType.ACTION:
ICommandAction action = (ICommandAction)result.Plugin;
if (action.RunType == InternalActionRunType.ON_STARTUP || action.RunType == InternalActionRunType.BOTH)
action.ExecuteStartup();
if(action.RunType == InternalActionRunType.ON_CALL || action.RunType == InternalActionRunType.BOTH)
Actions.Add(action);
OnActionLoaded?.Invoke(result);
break;
case PluginType.COMMAND:
Commands.Add((DBCommand)result.Plugin);
OnCommandLoaded?.Invoke(result);
@@ -9,6 +9,44 @@ namespace DiscordBotCore.Online.Helpers;
internal static class OnlineFunctions
{
/// <summary>
/// Copy one Stream to another <see langword="async" />
/// </summary>
/// <param name="stream">The base stream</param>
/// <param name="destination">The destination stream</param>
/// <param name="bufferSize">The buffer to read</param>
/// <param name="progress">The progress</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <exception cref="ArgumentNullException">Triggered if any <see cref="Stream" /> is empty</exception>
/// <exception cref="ArgumentOutOfRangeException">Triggered if <paramref name="bufferSize" /> is less then or equal to 0</exception>
/// <exception cref="InvalidOperationException">Triggered if <paramref name="stream" /> is not readable</exception>
/// <exception cref="ArgumentException">Triggered in <paramref name="destination" /> is not writable</exception>
public static async Task CopyToOtherStreamAsync(
this Stream stream, Stream destination, int bufferSize,
IProgress<long>? progress = null,
CancellationToken cancellationToken = default)
{
if (stream == null) throw new ArgumentNullException(nameof(stream));
if (destination == null) throw new ArgumentNullException(nameof(destination));
if (bufferSize <= 0) throw new ArgumentOutOfRangeException(nameof(bufferSize));
if (!stream.CanRead) throw new InvalidOperationException("The stream is not readable.");
if (!destination.CanWrite)
throw new ArgumentException("Destination stream is not writable", nameof(destination));
var buffer = new byte[bufferSize];
long totalBytesRead = 0;
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)
.ConfigureAwait(false)) != 0)
{
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
totalBytesRead += bytesRead;
progress?.Report(totalBytesRead);
}
}
/// <summary>
/// Downloads a <see cref="Stream" /> and saves it to another <see cref="Stream" />.
/// </summary>
+20 -26
View File
@@ -1,4 +1,5 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -88,7 +89,11 @@ public class PluginManager
public async Task AppendPluginToDatabase(PluginInfo pluginData)
{
List<PluginInfo> installedPlugins = await JsonManager.ConvertFromJson<List<PluginInfo>>(await File.ReadAllTextAsync(Application.CurrentApplication.PluginDatabase));
foreach (var dependency in pluginData.ListOfDependancies)
{
pluginData.ListOfDependancies[dependency.Key] = GenerateDependencyLocation(pluginData.PluginName, dependency.Value);
}
installedPlugins.Add(pluginData);
await JsonManager.SaveToJsonFile(Application.CurrentApplication.PluginDatabase, installedPlugins);
}
@@ -123,15 +128,19 @@ public class PluginManager
public async Task<bool> MarkPluginToUninstall(string pluginName)
{
List<PluginInfo> installedPlugins = await GetInstalledPlugins();
PluginInfo? info = installedPlugins.Find(info => info.PluginName == pluginName);
IEnumerable<PluginInfo> installedPlugins = await GetInstalledPlugins();
IEnumerable<PluginInfo> info = installedPlugins.Where(info => info.PluginName == pluginName).AsEnumerable();
if(info == null)
if(!info.Any())
return false;
await RemovePluginFromDatabase(pluginName);
info.IsMarkedToUninstall = true;
await AppendPluginToDatabase(info);
foreach (var item in info)
{
await RemovePluginFromDatabase(item.PluginName);
item.IsMarkedToUninstall = true;
await AppendPluginToDatabase(item);
}
return true;
@@ -139,11 +148,11 @@ public class PluginManager
public async Task UninstallMarkedPlugins()
{
List<PluginInfo> installedPlugins = await GetInstalledPlugins();
foreach(PluginInfo plugin in installedPlugins)
{
if(!plugin.IsMarkedToUninstall) continue;
IEnumerable<PluginInfo> installedPlugins = (await GetInstalledPlugins()).AsEnumerable();
IEnumerable<PluginInfo> pluginsToRemove = installedPlugins.Where(plugin => plugin.IsMarkedToUninstall).AsEnumerable();
foreach (var plugin in pluginsToRemove)
{
await UninstallPlugin(plugin);
}
}
@@ -171,21 +180,6 @@ public class PluginManager
throw new Exception("Dependency not found");
}
public async Task<string> GetDependencyLocation(string pluginName, string dependencyName)
{
PluginOnlineInfo? pluginData = await GetPluginDataByName(pluginName);
if(pluginData == null)
throw new Exception("Plugin not found");
var dependency = pluginData.Dependencies.Find(dep => dep.DependencyName == dependencyName);
if(dependency == null)
throw new Exception("Dependency not found");
return dependency.DownloadLocation;
}
public string GenerateDependencyLocation(string pluginName, string dependencyName)
{
return Path.Combine(Environment.CurrentDirectory, $"Libraries/{pluginName}/{dependencyName}");
@@ -8,31 +8,36 @@ namespace DiscordBotCore.Others.Actions;
public class InternalActionManager
{
public Dictionary<string, ICommandAction> Actions = new();
private readonly ActionsLoader _loader;
public InternalActionManager(string path, string extension)
{
_loader = new ActionsLoader(path, extension);
}
private Dictionary<string, ICommandAction> Actions = new();
public async Task Initialize()
{
var loadedActions = await _loader.Load();
Actions.Clear();
PluginLoader.Actions.ForEach(action =>
{
if (action.RunType == InternalActionRunType.ON_CALL || action.RunType == InternalActionRunType.BOTH)
{
if (this.Actions.ContainsKey(action.ActionName))
return; // ingore duplicates
if (loadedActions == null)
return;
foreach (var action in loadedActions)
Actions.TryAdd(action.ActionName, action);
this.Actions.Add(action.ActionName, action);
}
});
}
public async Task Refresh()
public IReadOnlyCollection<ICommandAction> GetActions()
{
Actions.Clear();
await Initialize();
return Actions.Values;
}
public bool Exists(string actionName)
{
return Actions.ContainsKey(actionName);
}
public ICommandAction GetAction(string actionName)
{
return Actions[actionName];
}
public async Task<bool> Execute(string actionName, params string[]? args)
+2 -1
View File
@@ -40,5 +40,6 @@ public enum PluginType
UNKNOWN,
COMMAND,
EVENT,
SLASH_COMMAND
SLASH_COMMAND,
ACTION
}
-83
View File
@@ -1,83 +0,0 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Discord;
namespace DiscordBotCore.Others;
/// <summary>
/// A special class with functions
/// </summary>
public static class Functions
{
/// <summary>
/// The location for the Resources folder
/// String: ./Data/Resources/
/// </summary>
public static string dataFolder => Application.CurrentApplication.DataFolder;
public static Color RandomColor
{
get
{
var random = new Random();
return new Color(random.Next(0, 255), random.Next(0, 255), random.Next(0, 255));
}
}
/// <summary>
/// Copy one Stream to another <see langword="async" />
/// </summary>
/// <param name="stream">The base stream</param>
/// <param name="destination">The destination stream</param>
/// <param name="bufferSize">The buffer to read</param>
/// <param name="progress">The progress</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <exception cref="ArgumentNullException">Triggered if any <see cref="Stream" /> is empty</exception>
/// <exception cref="ArgumentOutOfRangeException">Triggered if <paramref name="bufferSize" /> is less then or equal to 0</exception>
/// <exception cref="InvalidOperationException">Triggered if <paramref name="stream" /> is not readable</exception>
/// <exception cref="ArgumentException">Triggered in <paramref name="destination" /> is not writable</exception>
public static async Task CopyToOtherStreamAsync(
this Stream stream, Stream destination, int bufferSize,
IProgress<long>? progress = null,
CancellationToken cancellationToken = default)
{
if (stream == null) throw new ArgumentNullException(nameof(stream));
if (destination == null) throw new ArgumentNullException(nameof(destination));
if (bufferSize <= 0) throw new ArgumentOutOfRangeException(nameof(bufferSize));
if (!stream.CanRead) throw new InvalidOperationException("The stream is not readable.");
if (!destination.CanWrite)
throw new ArgumentException("Destination stream is not writable", nameof(destination));
var buffer = new byte[bufferSize];
long totalBytesRead = 0;
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)
.ConfigureAwait(false)) != 0)
{
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
totalBytesRead += bytesRead;
progress?.Report(totalBytesRead);
}
}
public static T SelectRandomValueOf<T>()
{
var enums = Enum.GetValues(typeof(T));
var random = new Random();
return (T)enums.GetValue(random.Next(enums.Length));
}
public static T RandomValue<T>(this T[] values)
{
Random random = new();
return values[random.Next(values.Length)];
}
public static string ToResourcesPath(this string path)
{
return Path.Combine(dataFolder, path);
}
}