Added external repo support

This commit is contained in:
2024-10-22 21:30:26 +03:00
parent f108a1fe08
commit 8a2212e47f
11 changed files with 224 additions and 141 deletions

View File

@@ -15,8 +15,8 @@ using DiscordBotCore.Others.Settings;
using DiscordBotCore.Modules;
using DiscordBotCore.Plugin;
using DiscordBotCore.Interfaces.PluginManager;
using DiscordBotCore.Interfaces.Modules;
using DiscordBotCore.Repository;
namespace DiscordBotCore
{
@@ -45,7 +45,7 @@ namespace DiscordBotCore
public string PluginDatabase => ApplicationEnvironmentVariables.Get<string>("PluginDatabase", _PluginsDatabaseFile);
public CustomSettingsDictionary ApplicationEnvironmentVariables { get; private set; } = null!;
public InternalActionManager InternalActionManager { get; private set; } = null!;
public IPluginManager PluginManager { get; private set; } = null!;
public PluginManager PluginManager { get; private set; } = null!;
/// <summary>
/// Create the application. This method is used to initialize the application. Can not initialize multiple times.
@@ -76,7 +76,7 @@ namespace DiscordBotCore
CurrentApplication.ApplicationEnvironmentVariables.Add("ResourceFolder", _ResourcesFolder);
CurrentApplication.ApplicationEnvironmentVariables.Add("LogsFolder", _LogsFolder);
CurrentApplication.ModuleManager = new ModuleManager();
CurrentApplication.ModuleManager = new ModuleManager(ModuleRepository.SolveRepo());
await CurrentApplication.ModuleManager.LoadModules();
var requirements = await CurrentApplication.ModuleManager.CheckRequiredModules();
if(requirements.RequireAny)
@@ -94,11 +94,8 @@ namespace DiscordBotCore
await JsonManager.SaveToJsonFile(_PluginsDatabaseFile, plugins);
}
#if DEBUG
CurrentApplication.PluginManager = new PluginManager("tests");
#else
CurrentApplication.PluginManager = new PluginManager();
#endif
CurrentApplication.PluginManager = new PluginManager(PluginRepository.SolveRepo());
await CurrentApplication.PluginManager.UninstallMarkedPlugins();
await CurrentApplication.PluginManager.CheckForUpdates();

View File

@@ -9,7 +9,7 @@ namespace DiscordBotCore.Database;
public class SqlDatabase
{
private readonly SQLiteConnection _connection;
private readonly SQLiteConnection _Connection;
/// <summary>
/// Initialize a SQL connection by specifing its private path
@@ -17,12 +17,10 @@ public class SqlDatabase
/// <param name="fileName">The path to the database (it is starting from ./Data/Resources/)</param>
public SqlDatabase(string fileName)
{
if (!fileName.StartsWith("./Data/Resources/"))
fileName = Path.Combine("./Data/Resources", fileName);
if (!File.Exists(fileName))
SQLiteConnection.CreateFile(fileName);
var connectionString = $"URI=file:{fileName}";
_connection = new SQLiteConnection(connectionString);
_Connection = new SQLiteConnection(connectionString);
}
@@ -32,7 +30,7 @@ public class SqlDatabase
/// <returns></returns>
public async Task Open()
{
await _connection.OpenAsync();
await _Connection.OpenAsync();
}
/// <summary>
@@ -55,7 +53,7 @@ public class SqlDatabase
query += ")";
var command = new SQLiteCommand(query, _connection);
var command = new SQLiteCommand(query, _Connection);
await command.ExecuteNonQueryAsync();
}
@@ -79,7 +77,7 @@ public class SqlDatabase
query += ")";
var command = new SQLiteCommand(query, _connection);
var command = new SQLiteCommand(query, _Connection);
command.ExecuteNonQuery();
}
@@ -94,7 +92,7 @@ public class SqlDatabase
{
var query = $"DELETE FROM {tableName} WHERE {KeyName} = '{KeyValue}'";
var command = new SQLiteCommand(query, _connection);
var command = new SQLiteCommand(query, _Connection);
await command.ExecuteNonQueryAsync();
}
@@ -109,7 +107,7 @@ public class SqlDatabase
{
var query = $"DELETE FROM {tableName} WHERE {KeyName} = '{KeyValue}'";
var command = new SQLiteCommand(query, _connection);
var command = new SQLiteCommand(query, _Connection);
command.ExecuteNonQuery();
}
@@ -225,7 +223,7 @@ public class SqlDatabase
/// <returns></returns>
public async void Stop()
{
await _connection.CloseAsync();
await _Connection.CloseAsync();
}
/// <summary>
@@ -237,7 +235,7 @@ public class SqlDatabase
/// <returns></returns>
public async Task AddColumnsToTableAsync(string tableName, string[] columns, string TYPE = "TEXT")
{
var command = _connection.CreateCommand();
var command = _Connection.CreateCommand();
command.CommandText = $"SELECT * FROM {tableName}";
var reader = await command.ExecuteReaderAsync();
var tableColumns = new List<string>();
@@ -261,7 +259,7 @@ public class SqlDatabase
/// <returns></returns>
public void AddColumnsToTable(string tableName, string[] columns, string TYPE = "TEXT")
{
var command = _connection.CreateCommand();
var command = _Connection.CreateCommand();
command.CommandText = $"SELECT * FROM {tableName}";
var reader = command.ExecuteReader();
var tableColumns = new List<string>();
@@ -283,7 +281,7 @@ public class SqlDatabase
/// <returns>True if the table exists, false if not</returns>
public async Task<bool> TableExistsAsync(string tableName)
{
var cmd = _connection.CreateCommand();
var cmd = _Connection.CreateCommand();
cmd.CommandText = $"SELECT name FROM sqlite_master WHERE type='table' AND name='{tableName}'";
var result = await cmd.ExecuteScalarAsync();
@@ -299,7 +297,7 @@ public class SqlDatabase
/// <returns>True if the table exists, false if not</returns>
public bool TableExists(string tableName)
{
var cmd = _connection.CreateCommand();
var cmd = _Connection.CreateCommand();
cmd.CommandText = $"SELECT name FROM sqlite_master WHERE type='table' AND name='{tableName}'";
var result = cmd.ExecuteScalar();
@@ -316,7 +314,7 @@ public class SqlDatabase
/// <returns></returns>
public async Task CreateTableAsync(string tableName, params string[] columns)
{
var cmd = _connection.CreateCommand();
var cmd = _Connection.CreateCommand();
cmd.CommandText = $"CREATE TABLE IF NOT EXISTS {tableName} ({string.Join(", ", columns)})";
await cmd.ExecuteNonQueryAsync();
}
@@ -329,7 +327,7 @@ public class SqlDatabase
/// <returns></returns>
public void CreateTable(string tableName, params string[] columns)
{
var cmd = _connection.CreateCommand();
var cmd = _Connection.CreateCommand();
cmd.CommandText = $"CREATE TABLE IF NOT EXISTS {tableName} ({string.Join(", ", columns)})";
cmd.ExecuteNonQuery();
}
@@ -341,9 +339,9 @@ public class SqlDatabase
/// <returns>The number of rows that the query modified</returns>
public async Task<int> ExecuteAsync(string query)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
var command = new SQLiteCommand(query, _connection);
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
var command = new SQLiteCommand(query, _Connection);
var answer = await command.ExecuteNonQueryAsync();
return answer;
}
@@ -355,9 +353,9 @@ public class SqlDatabase
/// <returns>The number of rows that the query modified</returns>
public int Execute(string query)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
_connection.Open();
var command = new SQLiteCommand(query, _connection);
if (!_Connection.State.HasFlag(ConnectionState.Open))
_Connection.Open();
var command = new SQLiteCommand(query, _Connection);
var r = command.ExecuteNonQuery();
return r;
@@ -370,9 +368,9 @@ public class SqlDatabase
/// <returns>The result is a string that has all values separated by space character</returns>
public async Task<string?> ReadDataAsync(string query)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
var command = new SQLiteCommand(query, _connection);
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
var command = new SQLiteCommand(query, _Connection);
var reader = await command.ExecuteReaderAsync();
var values = new object[reader.FieldCount];
@@ -393,10 +391,10 @@ public class SqlDatabase
/// <returns>The result is a string that has all values separated by space character</returns>
public async Task<string?> ReadDataAsync(string query, params KeyValuePair<string, object>[] parameters)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
var command = new SQLiteCommand(query, _connection);
var command = new SQLiteCommand(query, _Connection);
foreach (var parameter in parameters)
{
var p = CreateParameter(parameter);
@@ -423,9 +421,9 @@ public class SqlDatabase
/// <returns>The result is a string that has all values separated by space character</returns>
public string? ReadData(string query)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
_connection.Open();
var command = new SQLiteCommand(query, _connection);
if (!_Connection.State.HasFlag(ConnectionState.Open))
_Connection.Open();
var command = new SQLiteCommand(query, _Connection);
var reader = command.ExecuteReader();
var values = new object[reader.FieldCount];
@@ -445,9 +443,9 @@ public class SqlDatabase
/// <returns>The first row as separated items</returns>
public async Task<object[]?> ReadDataArrayAsync(string query)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
var command = new SQLiteCommand(query, _connection);
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
var command = new SQLiteCommand(query, _Connection);
var reader = await command.ExecuteReaderAsync();
var values = new object[reader.FieldCount];
@@ -462,10 +460,10 @@ public class SqlDatabase
public async Task<object[]?> ReadDataArrayAsync(string query, params KeyValuePair<string, object>[] parameters)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
var command = new SQLiteCommand(query, _connection);
var command = new SQLiteCommand(query, _Connection);
foreach (var parameter in parameters)
{
var p = CreateParameter(parameter);
@@ -494,9 +492,9 @@ public class SqlDatabase
/// <returns>The first row as separated items</returns>
public object[]? ReadDataArray(string query)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
_connection.Open();
var command = new SQLiteCommand(query, _connection);
if (!_Connection.State.HasFlag(ConnectionState.Open))
_Connection.Open();
var command = new SQLiteCommand(query, _Connection);
var reader = command.ExecuteReader();
var values = new object[reader.FieldCount];
@@ -517,9 +515,9 @@ public class SqlDatabase
/// <returns>A list of string arrays representing the values that the query returns</returns>
public async Task<List<string[]>?> ReadAllRowsAsync(string query)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
var command = new SQLiteCommand(query, _connection);
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
var command = new SQLiteCommand(query, _Connection);
var reader = await command.ExecuteReaderAsync();
if (!reader.HasRows)
@@ -614,10 +612,10 @@ public class SqlDatabase
/// <returns>The number of rows that the query modified in the database</returns>
public async Task<int> ExecuteNonQueryAsync(string query, params KeyValuePair<string, object>[] parameters)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
var command = new SQLiteCommand(query, _connection);
var command = new SQLiteCommand(query, _Connection);
foreach (var parameter in parameters)
{
var p = CreateParameter(parameter);
@@ -638,10 +636,10 @@ public class SqlDatabase
/// <returns>An object of type T that represents the output of the convertor function based on the array of objects that the first row of the result has</returns>
public async Task<T?> ReadObjectOfTypeAsync<T>(string query, Func<object[], T> convertor, params KeyValuePair<string, object>[] parameters)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
var command = new SQLiteCommand(query, _connection);
var command = new SQLiteCommand(query, _Connection);
foreach (var parameter in parameters)
{
var p = CreateParameter(parameter);
@@ -672,10 +670,10 @@ public class SqlDatabase
public async Task<List<T>> ReadListOfTypeAsync<T>(string query, Func<object[], T> convertor,
params KeyValuePair<string, object>[] parameters)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
var command = new SQLiteCommand(query, _connection);
var command = new SQLiteCommand(query, _Connection);
foreach (var parameter in parameters)
{
var p = CreateParameter(parameter);

View File

@@ -9,6 +9,7 @@ using DiscordBotCore.Loaders;
using DiscordBotCore.Online;
using DiscordBotCore.Others;
using DiscordBotCore.Others.Exceptions;
using DiscordBotCore.Repository;
using Newtonsoft.Json;
namespace DiscordBotCore.Modules
@@ -30,8 +31,8 @@ namespace DiscordBotCore.Modules
private static readonly string _BaseModuleFolder = "./Data/Modules";
private static readonly string _BaseModuleConfig = "./Data/Resources/modules.json";
private const string _ModuleDatabase = "https://raw.githubusercontent.com/andreitdr/SethPlugins/tests/modules.json";
// private const string _ModuleDatabase = "https://raw.githubusercontent.com/andreitdr/SethPlugins/tests/modules.json";
private ModuleRepository _ModuleRepository;
private List<LoadedModule> Modules { get; }
public IEnumerable<ModuleData> GetLocalModules()
@@ -39,10 +40,11 @@ namespace DiscordBotCore.Modules
return Modules.Select(module => module.ModuleData);
}
internal ModuleManager()
internal ModuleManager(ModuleRepository moduleRepository)
{
Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("ModuleFolder", _BaseModuleFolder);
Modules = new();
_ModuleRepository = moduleRepository;
}
public async Task<ModuleOnlineData?> ServerGetModuleWithName(string moduleName)
@@ -53,7 +55,7 @@ namespace DiscordBotCore.Modules
public async Task<List<ModuleOnlineData>> ServerGetAllModules(ModuleType? moduleTypeFilter = null)
{
string jsonDatabaseRemote = await ServerCom.GetAllTextFromUrl(_ModuleDatabase);
var jsonDatabaseRemote = await _ModuleRepository.JsonGetAllModules();
var modules = await JsonManager.ConvertFromJson<List<ModuleOnlineData>>(jsonDatabaseRemote);

View File

@@ -3,69 +3,50 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using DiscordBotCore.Interfaces.PluginManager;
using DiscordBotCore.Others;
using DiscordBotCore.Plugin;
using DiscordBotCore.Repository;
using DiscordBotCore.Updater.Plugins;
namespace DiscordBotCore.Online;
public class PluginManager : IPluginManager
public class PluginManager
{
private static readonly string _DefaultBranch = "releases";
private static readonly string _DefaultBaseUrl = "https://raw.githubusercontent.com/andreitdr/SethPlugins";
private PluginRepository _PluginRepository;
private static readonly string _DefaultPluginsLink = "PluginsList.json";
public string Branch { get; set; }
public string BaseUrl { get; set; }
private string PluginsLink => $"{BaseUrl}/{Branch}/{_DefaultPluginsLink}";
public PluginManager(Uri baseUrl, string branch)
public PluginManager(PluginRepository pluginRepository)
{
BaseUrl = baseUrl.ToString();
Branch = branch;
}
public PluginManager(string branch)
{
BaseUrl = _DefaultBaseUrl;
Branch = branch;
}
public PluginManager()
{
BaseUrl = _DefaultBaseUrl;
Branch = _DefaultBranch;
_PluginRepository = pluginRepository;
}
public async Task<List<PluginOnlineInfo>?> GetPluginsList()
{
var jsonText = await ServerCom.GetAllTextFromUrl(PluginsLink);
List<PluginOnlineInfo> result = await JsonManager.ConvertFromJson<List<PluginOnlineInfo>>(jsonText);
var jsonText = await _PluginRepository.JsonGetAllPlugins();
List<PluginOnlineInfo> result = await JsonManager.ConvertFromJson<List<PluginOnlineInfo>>(jsonText);
var currentOS = OperatingSystem.IsWindows() ? OSType.WINDOWS :
var currentOs = OperatingSystem.IsWindows() ? OSType.WINDOWS :
OperatingSystem.IsLinux() ? OSType.LINUX :
OperatingSystem.IsMacOS() ? OSType.MACOSX : OSType.NONE;
return result.FindAll(pl => (pl.SupportedOS & currentOS) != 0);
return result.FindAll(pl => (pl.SupportedOS & currentOs) != 0);
}
public async Task<PluginOnlineInfo?> GetPluginDataByName(string pluginName)
{
List<PluginOnlineInfo>? plugins = await GetPluginsList();
if (plugins == null)
if (plugins is null)
{
return null;
}
// try to get the best matching plugin using the pluginName as a search query
PluginOnlineInfo? result = plugins.Find(pl => pl.Name.ToLower().Contains(pluginName.ToLower()));
if (result == null) return null;
PluginOnlineInfo? result = plugins.Find(pl => pl.Name.Contains(pluginName, StringComparison.CurrentCultureIgnoreCase));
if (result is null)
{
return null;
}
return result;
}

View File

@@ -6,6 +6,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using DiscordBotCore.Online.Helpers;
using DiscordBotCore.Others;
namespace DiscordBotCore.Online;

View File

@@ -0,0 +1,49 @@
using System;
using System.Threading.Tasks;
using DiscordBotCore.Online;
namespace DiscordBotCore.Repository;
public sealed class ModuleRepository : RepositoryBase
{
public static readonly ModuleRepository Default = new ModuleRepository("Testing", "https://wizzy-server.ro/SethDiscordBot/ModulesRepo", "modules.json");
private ModuleRepository(string repositoryName, string repositoryUrl, string databaseFile) : base(repositoryName, repositoryUrl, databaseFile)
{
}
public async Task<string> JsonGetAllModules()
{
var jsonResponse = await ServerCom.GetAllTextFromUrl(DatabasePath);
return jsonResponse;
}
private static ModuleRepository From(string repositoryName, string repositoryUrl, string databaseFile)
{
return new ModuleRepository(repositoryName, repositoryUrl, databaseFile);
}
internal static ModuleRepository SolveRepo()
{
if (!Application.CurrentApplication.ApplicationEnvironmentVariables.ContainsKey("ModuleRepository"))
{
return Default;
}
try
{
var moduleRepoDict = Application.CurrentApplication.ApplicationEnvironmentVariables.GetDictionary<string, string>("ModuleRepository");
var moduleRepo = From(
moduleRepoDict["Name"],
moduleRepoDict["Url"],
moduleRepoDict["DatabaseFile"]
);
return moduleRepo;
}
catch(Exception ex)
{
Application.Logger.LogException(ex, Application.CurrentApplication);
return Default;
}
}
}

View File

@@ -0,0 +1,52 @@
using System;
using System.Threading.Tasks;
using DiscordBotCore.Online;
namespace DiscordBotCore.Repository;
public sealed class PluginRepository : RepositoryBase
{
public static readonly PluginRepository Default = new PluginRepository("Testing", "https://wizzy-server.ro/SethDiscordBot/PluginsRepo", "PluginsList.json");
private PluginRepository(string repositoryName, string repositoryUrl, string databaseFile) : base(repositoryName, repositoryUrl, databaseFile)
{
}
private static PluginRepository From(string repositoryName, string repositoryUrl, string databaseFile)
{
return new PluginRepository(repositoryName, repositoryUrl, databaseFile);
}
public async Task<string> JsonGetAllPlugins()
{
var jsonResponse = await ServerCom.GetAllTextFromUrl(DatabasePath);
return jsonResponse;
}
internal static PluginRepository SolveRepo()
{
if (!Application.CurrentApplication.ApplicationEnvironmentVariables.ContainsKey("PluginRepository"))
{
return Default;
}
try
{
var pluginRepoDict = Application.CurrentApplication.ApplicationEnvironmentVariables.GetDictionary<string, string>("PluginRepository");
var pluginRepo = PluginRepository.From(
pluginRepoDict["Name"],
pluginRepoDict["Url"],
pluginRepoDict["DatabaseFile"]
);
return pluginRepo;
}
catch(Exception ex)
{
Application.Logger.LogException(ex, Application.CurrentApplication);
return Default;
}
}
}

View File

@@ -0,0 +1,33 @@
namespace DiscordBotCore.Repository;
public abstract class RepositoryBase
{
/*
Example of JSON for config file :
Please mind that the URL should not end with a slash. !
"PluginRepository": {
"Name": "Default Plugins Repository",
"Url": "http://blahblahblah.blah/MyCustomRepo",
"DatabaseFile": "PluginsList.json"
},
"ModuleRepository": {
"Name": "Default Modules Repository",
"Url": "http://blahblahblah.blah/MyCustomRepo",
"DatabaseFile": "modules.json"
}
*/
public string RepositoryName { get; init; }
private string RepositoryUrl { get; init; }
private string DatabaseFile { get; init; }
protected string DatabasePath => $"{RepositoryUrl}/{DatabaseFile}";
protected RepositoryBase(string repositoryName, string repositoryUrl, string databaseFile)
{
RepositoryName = repositoryName;
RepositoryUrl = repositoryUrl;
DatabaseFile = databaseFile;
}
}

View File

@@ -17,16 +17,16 @@ public class PluginUpdater
{
_PluginsManager = pluginManager;
}
public async Task<PluginOnlineInfo> GetPluginInfo(string pluginName)
private async Task<PluginOnlineInfo> GetPluginInfo(string pluginName)
{
var result = await _PluginsManager.GetPluginDataByName(pluginName);
if(result is null)
throw new PluginNotFoundException(pluginName, _PluginsManager.BaseUrl, _PluginsManager.Branch);
throw new PluginNotFoundException(pluginName);
return result;
}
public async Task<PluginInfo> GetLocalPluginInfo(string pluginName)
private async Task<PluginInfo> GetLocalPluginInfo(string pluginName)
{
string pluginsDatabase = File.ReadAllText(DiscordBotCore.Application.CurrentApplication.PluginDatabase);
List<PluginInfo> installedPlugins = await JsonManager.ConvertFromJson<List<PluginInfo>>(pluginsDatabase);
@@ -42,8 +42,9 @@ public class PluginUpdater
PluginOnlineInfo pluginInfo = await GetPluginInfo(pluginName);
await ServerCom.DownloadFileAsync(pluginInfo.DownLoadLink, $"{DiscordBotCore.Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("PluginFolder")}/{pluginName}.dll", progressMeter);
foreach(OnlineDependencyInfo dependency in pluginInfo.Dependencies)
await ServerCom.DownloadFileAsync(dependency.DownloadLink, dependency.DownloadLocation, progressMeter);
if(pluginInfo.Dependencies is not null)
foreach(OnlineDependencyInfo dependency in pluginInfo.Dependencies)
await ServerCom.DownloadFileAsync(dependency.DownloadLink, dependency.DownloadLocation, progressMeter);
await _PluginsManager.RemovePluginFromDatabase(pluginName);
await _PluginsManager.AppendPluginToDatabase(PluginInfo.FromOnlineInfo(pluginInfo));