Updated API for plugins to work with database from remote. Added PluginRepository and removed Installation Scripts

This commit is contained in:
2025-01-26 20:34:34 +02:00
parent 8b2169dc7b
commit 84b19e2069
35 changed files with 435 additions and 1056 deletions

View File

@@ -53,7 +53,7 @@ namespace DiscordBot.Bot.Actions
Application.CurrentApplication.Logger.Log("The plugin name is invalid", LogType.Error);
}
PluginInfo pluginInfo = new PluginInfo(args[^1], new(1, 0, 0), [], false, true, args.Contains("-enabled"));
PluginInfo pluginInfo = new PluginInfo(args[^1], "1.0.0", [], false, true, args.Contains("-enabled"));
Application.CurrentApplication.Logger.Log("Adding plugin: " + args[^1]);
await Application.CurrentApplication.PluginManager.AppendPluginToDatabase(pluginInfo);
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using DiscordBot.Utilities;
@@ -22,51 +23,20 @@ internal static class PluginMethods
{
Console.WriteLine($"Fetching plugin list ...");
var data = await ConsoleUtilities.ExecuteWithProgressBar(Application.CurrentApplication.PluginManager.GetPluginsList(), "Reading remote database");
TableData tableData = new(["Name", "Description", "Version", "Installed", "Dependencies", "Enabled"]);
var onlinePlugins = await ConsoleUtilities.ExecuteWithProgressBar(Application.CurrentApplication.PluginManager.GetPluginsList(), "Reading remote database");
var installedPlugins = await ConsoleUtilities.ExecuteWithProgressBar(Application.CurrentApplication.PluginManager.GetInstalledPlugins(), "Reading local database ");
TableData tableData = new(["Name", "Description", "Author", "Latest Version", "Installed Version"]);
foreach (var plugin in data)
foreach (var onlinePlugin in onlinePlugins)
{
bool isInstalled = installedPlugins.Any(p => p.PluginName == plugin.Name);
if (!plugin.HasFileDependencies)
{
tableData.AddRow([plugin.Name, plugin.Description,
plugin.Version.ToString(), isInstalled ? "[green]Yes[/]" : "[red]No[/]", "None",
isInstalled ? installedPlugins.First(p=>p.PluginName == plugin.Name).IsEnabled ? "[green]Enabled[/]" : "[red]Disabled[/]" : "[yellow]NOT INSTALLED[/]"]);
continue;
}
TableData dependenciesTable;
if (isInstalled)
{
dependenciesTable = new(["Name", "Location", "Is Executable"]);
foreach (var dep in plugin.Dependencies)
{
dependenciesTable.AddRow([dep.DependencyName, dep.DownloadLocation, dep.IsExecutable ? "Yes" : "No"]);
}
}
else
{
dependenciesTable = new(["Name", "Is Executable"]);
foreach (var dep in plugin.Dependencies)
{
dependenciesTable.AddRow([dep.DependencyName, dep.IsExecutable ? "Yes" : "No"]);
}
}
dependenciesTable.DisplayLinesBetweenRows = true;
Table spectreTable = dependenciesTable.AsTable();
tableData.AddRow([plugin.Name, plugin.Description, plugin.Version.ToString(), isInstalled ? "[green]Yes[/]" : "[red]No[/]", spectreTable,
isInstalled ? installedPlugins.First(p=>p.PluginName == plugin.Name).IsEnabled ? "Enabled" : "[red]Disabled[/]" : "[yellow]NOT INSTALLED[/]"]);
bool isInstalled = installedPlugins.Any(p => p.PluginName == onlinePlugin.PluginName);
tableData.AddRow([
onlinePlugin.PluginName,
onlinePlugin.PluginDescription,
onlinePlugin.PluginAuthor,
onlinePlugin.LatestVersion,
isInstalled ? installedPlugins.First(p => p.PluginName == onlinePlugin.PluginName).PluginVersion : "Not Installed"
]);
}
tableData.HasRoundBorders = false;
@@ -100,113 +70,24 @@ internal static class PluginMethods
await Application.CurrentApplication.PluginManager.SetEnabledStatus(pluginName, true);
}
internal static async Task DownloadPlugin(string pluginName)
public static async Task DownloadPluginWithParallelDownloads(string pluginName)
{
var pluginData = await Application.CurrentApplication.PluginManager.GetPluginDataByName(pluginName);
OnlinePlugin? pluginData = await Application.CurrentApplication.PluginManager.GetPluginDataByName(pluginName);
if (pluginData is null)
{
Console.WriteLine($"Plugin {pluginName} not found. Please check the spelling and try again.");
return;
}
// rename the plugin to the name of the plugin
pluginName = pluginData.Name;
var pluginLink = pluginData.DownLoadLink;
await AnsiConsole.Progress()
.Columns(new ProgressColumn[]
{
new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn()
}
)
.StartAsync(async ctx =>
{
var downloadTask = ctx.AddTask("Downloading plugin...");
IProgress<float> progress = new Progress<float>(p => { downloadTask.Value = p; });
string baseFolder = Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("PluginFolder");
await ServerCom.DownloadFileAsync(pluginLink, $"{baseFolder}/{pluginData.Name}.dll", progress);
downloadTask.Increment(100);
ctx.Refresh();
}
);
if (!pluginData.HasFileDependencies)
{
if (pluginData.HasScriptDependencies)
{
Console.WriteLine("Executing post install scripts ...");
await Application.CurrentApplication.PluginManager.ExecutePluginInstallScripts(pluginData.ScriptDependencies);
}
PluginInfo pluginInfo = new(pluginName, pluginData.Version, []);
Console.WriteLine("Finished installing " + pluginName + " successfully");
await Application.CurrentApplication.PluginManager.AppendPluginToDatabase(pluginInfo);
await RefreshPlugins(false);
return;
}
List<Tuple<ProgressTask, IProgress<float>, string, string>> downloadTasks = new();
await AnsiConsole.Progress()
.Columns(new ProgressColumn[]
{
new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn()
}
)
.StartAsync(async ctx =>
{
foreach (var dependency in pluginData.Dependencies)
{
var task = ctx.AddTask($"Downloading {dependency.DownloadLocation} -> Executable: {dependency.IsExecutable}: ");
IProgress<float> progress = new Progress<float>(p =>
{
task.Value = p;
}
);
task.IsIndeterminate = true;
downloadTasks.Add(new Tuple<ProgressTask, IProgress<float>, string, string>(task, progress, dependency.DownloadLink, dependency.DownloadLocation));
}
int maxParallelDownloads = Application.CurrentApplication.ApplicationEnvironmentVariables.Get("MaxParallelDownloads", 5);
var options = new ParallelOptions()
{
MaxDegreeOfParallelism = maxParallelDownloads,
TaskScheduler = TaskScheduler.Default
};
await Parallel.ForEachAsync(downloadTasks, options, async (tuple, token) =>
{
tuple.Item1.IsIndeterminate = false;
string downloadLocation = Application.CurrentApplication.PluginManager.GenerateDependencyRelativePath(pluginName, tuple.Item4);
await ServerCom.DownloadFileAsync(tuple.Item3, downloadLocation, tuple.Item2);
}
);
}
);
var result = await Application.CurrentApplication.PluginManager.GatherInstallDataForPlugin(pluginData);
List<Tuple<string, string>> downloadList = result.Item1.Select(kvp => new Tuple<string, string>(kvp.Key, kvp.Value)).ToList();
await ConsoleUtilities.ExecuteParallelDownload(FileDownloader.CreateDownloadTask, new HttpClient(), downloadList, "Downloading:");
if(pluginData.HasScriptDependencies)
{
Console.WriteLine("Executing post install scripts ...");
await Application.CurrentApplication.PluginManager.ExecutePluginInstallScripts(pluginData.ScriptDependencies);
}
await Application.CurrentApplication.PluginManager.AppendPluginToDatabase(PluginInfo.FromOnlineInfo(pluginData));
await RefreshPlugins(false);
await Application.CurrentApplication.PluginManager.AppendPluginToDatabase(PluginInfo.FromOnlineInfo(pluginData, result.Item2));
}
internal static async Task<bool> LoadPlugins(string[] args)
{

View File

@@ -118,7 +118,7 @@ public class Plugin: ICommandAction
}
}
await PluginMethods.DownloadPlugin(pluginName);
await PluginMethods.DownloadPluginWithParallelDownloads(pluginName);
break;
}

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>disable</Nullable>
<Nullable>enable</Nullable>
<ApplicationIcon />
<StartupObject />
<SignAssembly>False</SignAssembly>

View File

@@ -4,11 +4,9 @@ using System.Reflection;
using System.Threading.Tasks;
using DiscordBot.Bot.Actions.Extra;
using DiscordBot.Utilities;
using DiscordBotCore;
using DiscordBotCore.Bot;
using DiscordBotCore.Others;
using DiscordBotCore.Updater.Application;
using Spectre.Console;
@@ -81,14 +79,6 @@ public class Program
{
await Application.CreateApplication();
AppUpdater updater = new AppUpdater();
Update? update = await updater.PrepareUpdate();
if(update is not null)
{
await ConsoleUtilities.ExecuteTaskWithBuiltInProgress(updater.SelfUpdate, update, "Discord Bot Update");
return;
}
void LogMessageFunction(string message, LogType logType)
{
string messageAsString = message;

View File

@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using DiscordBotCore.Updater.Application;
using DiscordBotCore.Online;
using Spectre.Console;
namespace DiscordBot.Utilities;
@@ -64,4 +67,22 @@ internal static class ConsoleUtilities
}
public static async Task ExecuteParallelDownload(Func<HttpClient, string, string, IProgress<float>, Task> method, HttpClient client,
List<Tuple<string, string>> parameters, string taskMessage)
{
await AnsiConsole.Progress().AutoClear(false).HideCompleted(false)
.Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn())
.StartAsync(async ctx =>
{
var tasks = new List<Task>();
foreach (var (location, url) in parameters)
{
var task = ctx.AddTask(taskMessage + " " + url);
IProgress<float> progress = new Progress<float>(x => task.Value = x * 100);
tasks.Add(method(client, url, location, progress));
}
await Task.WhenAll(tasks);
});
}
}

View File

@@ -16,14 +16,14 @@ public class PluginInstallEndpoint : IEndpoint
Dictionary<string, string> jsonDict = await JsonManager.ConvertFromJson<Dictionary<string, string>>(jsonRequest);
string pluginName = jsonDict["pluginName"];
PluginOnlineInfo? pluginInfo = await Application.CurrentApplication.PluginManager.GetPluginDataByName(pluginName);
OnlinePlugin? pluginInfo = await Application.CurrentApplication.PluginManager.GetPluginDataByName(pluginName);
if (pluginInfo == null)
{
return ApiResponse.Fail("Plugin not found.");
}
Application.CurrentApplication.PluginManager.InstallPluginWithNoProgress(pluginInfo);
Application.CurrentApplication.PluginManager.InstallPluginNoProgress(pluginInfo);
return ApiResponse.Ok();
}
}

View File

@@ -14,9 +14,9 @@ public class PluginListEndpoint : IEndpoint
var onlineInfos = await Application.CurrentApplication.PluginManager.GetPluginsList();
var response = await JsonManager.ConvertToJson(onlineInfos, [
nameof(PluginOnlineInfo.Name),
nameof(PluginOnlineInfo.Author),
nameof(PluginOnlineInfo.Description)
nameof(OnlinePlugin.PluginName),
nameof(OnlinePlugin.PluginAuthor),
nameof(OnlinePlugin.PluginDescription)
]);
return ApiResponse.From(response, true);

View File

@@ -14,7 +14,6 @@ using DiscordBotCore.Others.Actions;
using DiscordBotCore.Others.Settings;
using DiscordBotCore.Plugin;
using DiscordBotCore.Logging;
using DiscordBotCore.Repository;
namespace DiscordBotCore
{
@@ -55,12 +54,6 @@ namespace DiscordBotCore
/// </summary>
public static async Task CreateApplication()
{
if (!await OnlineFunctions.IsInternetConnected())
{
Console.WriteLine("The main repository server is not reachable. Please check your internet connection.");
Environment.Exit(0);
}
if (CurrentApplication is not null)
{
CurrentApplication.Logger.Log("Application is already initialized. Reinitialization is not allowed", LogType.Error);
@@ -87,32 +80,13 @@ namespace DiscordBotCore
await JsonManager.SaveToJsonFile(_PluginsDatabaseFile, plugins);
}
CurrentApplication.PluginManager = new PluginManager(PluginRepository.SolveRepo());
CurrentApplication.PluginManager = new PluginManager(new PluginRepository(PluginRepositoryConfiguration.Default));
await CurrentApplication.PluginManager.UninstallMarkedPlugins();
await CurrentApplication.PluginManager.CheckForUpdates();
CurrentApplication.InternalActionManager = new InternalActionManager();
await CurrentApplication.InternalActionManager.Initialize();
if (OperatingSystem.IsWindows())
{
CurrentApplication.ApplicationEnvironmentVariables.Add("console.terminal", "cmd");
CurrentApplication.ApplicationEnvironmentVariables.Add("console.cmd_prefix", "/c ");
}
if(OperatingSystem.IsLinux())
{
CurrentApplication.ApplicationEnvironmentVariables.Add("console.terminal", "bash");
CurrentApplication.ApplicationEnvironmentVariables.Add("console.cmd_prefix", string.Empty);
}
if(OperatingSystem.IsMacOS())
{
CurrentApplication.ApplicationEnvironmentVariables.Add("console.terminal", "sh");
CurrentApplication.ApplicationEnvironmentVariables.Add("console.cmd_prefix", string.Empty);
}
IsRunning = true;
}
@@ -165,7 +139,7 @@ namespace DiscordBotCore
return result;
}
public static void Log(string message, LogType? logType = LogType.Info)
public static void Log(string message, LogType logType = LogType.Info)
{
CurrentApplication.Logger.Log(message, logType);
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using DiscordBotCore.Plugin;
namespace DiscordBotCore.Interfaces.PluginManagement;
public interface IPluginRepository
{
public Task<List<OnlinePlugin>> GetAllPlugins();
public Task<OnlinePlugin?> GetPluginById(int pluginId);
public Task<OnlinePlugin?> GetPluginByName(string pluginName);
public Task<List<OnlineDependencyInfo>> GetDependenciesForPlugin(int pluginId);
}

View File

@@ -0,0 +1,11 @@
namespace DiscordBotCore.Interfaces.PluginManagement;
public interface IPluginRepositoryConfiguration
{
public string BaseUrl { get; }
public string PluginRepositoryLocation { get; }
public string DependenciesRepositoryLocation { get; }
}

View File

@@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using DiscordBotCore.Plugin;
namespace DiscordBotCore.Interfaces.PluginManager
{
public interface IPluginManager
{
public string BaseUrl { get; set; }
public string Branch { get; set; }
Task AppendPluginToDatabase(PluginInfo pluginData);
Task CheckForUpdates();
Task ExecutePluginInstallScripts(List<OnlineScriptDependencyInfo> listOfDependencies);
string GenerateDependencyRelativePath(string pluginName, string dependencyPath);
Task<string?> GetDependencyLocation(string dependencyName);
Task<string?> GetDependencyLocation(string pluginName, string dependencyName);
Task<List<PluginInfo>> GetInstalledPlugins();
Task<PluginOnlineInfo?> GetPluginDataByName(string pluginName);
Task<List<PluginOnlineInfo>?> GetPluginsList();
Task InstallPlugin(PluginOnlineInfo pluginData, IProgress<float>? installProgress);
Task<bool> IsPluginInstalled(string pluginName);
Task<bool> MarkPluginToUninstall(string pluginName);
Task RemovePluginFromDatabase(string pluginName);
Task UninstallMarkedPlugins();
Task SetEnabledStatus(string pluginName, bool status);
}
}

View File

@@ -1,81 +0,0 @@
using System;
using System.Reflection;
namespace DiscordBotCore.Interfaces.Updater
{
public class AppVersion : IVersion
{
public static readonly AppVersion CurrentAppVersion = new AppVersion(Assembly.GetEntryAssembly().GetName().Version.ToString());
public int Major { get; set; }
public int Minor { get; set; }
public int Patch { get; set; }
public int PatchVersion { get; set; }
private readonly char _Separator = '.';
public AppVersion(string versionAsString)
{
string[] versionParts = versionAsString.Split(_Separator);
if (versionParts.Length != 4)
{
throw new ArgumentException("Invalid version string");
}
Major = int.Parse(versionParts[0]);
Minor = int.Parse(versionParts[1]);
Patch = int.Parse(versionParts[2]);
PatchVersion = int.Parse(versionParts[3]);
}
public bool IsNewerThan(IVersion version)
{
if (Major > version.Major)
return true;
if (Major == version.Major && Minor > version.Minor)
return true;
if (Major == version.Major && Minor == version.Minor && Patch > version.Patch)
return true;
if (Major == version.Major && Minor == version.Minor && Patch == version.Patch && PatchVersion > version.PatchVersion)
return true;
return false;
}
public bool IsOlderThan(IVersion version)
{
if (Major < version.Major)
return true;
if (Major == version.Major && Minor < version.Minor)
return true;
if (Major == version.Major && Minor == version.Minor && Patch < version.Patch)
return true;
if (Major == version.Major && Minor == version.Minor && Patch == version.Patch && PatchVersion < version.PatchVersion)
return true;
return false;
}
public bool IsEqualTo(IVersion version)
{
return Major == version.Major && Minor == version.Minor && Patch == version.Patch && PatchVersion == version.PatchVersion;
}
public string ToShortString()
{
return $"{Major}.{Minor}.{Patch}.{PatchVersion}";
}
public override string ToString()
{
return ToShortString();
}
}
}

View File

@@ -1,17 +0,0 @@
namespace DiscordBotCore.Interfaces.Updater;
public interface IVersion
{
public int Major { get; }
public int Minor { get; }
public int Patch { get; }
public int PatchVersion => 0;
public bool IsNewerThan(IVersion version);
public bool IsOlderThan(IVersion version);
public bool IsEqualTo(IVersion version);
public string ToShortString();
}

View File

@@ -1,76 +0,0 @@
using System;
namespace DiscordBotCore.Interfaces.Updater;
public abstract class Version: IVersion
{
public int Major { get; }
public int Minor { get; }
public int Patch { get; }
protected readonly char _Separator = '.';
protected Version(int major, int minor, int patch)
{
Major = major;
Minor = minor;
Patch = patch;
}
protected Version(string versionAsString)
{
string[] versionParts = versionAsString.Split(_Separator);
if (versionParts.Length != 3)
{
throw new ArgumentException("Invalid version string");
}
Major = int.Parse(versionParts[0]);
Minor = int.Parse(versionParts[1]);
Patch = int.Parse(versionParts[2]);
}
public bool IsNewerThan(IVersion version)
{
if (Major > version.Major)
return true;
if (Major == version.Major && Minor > version.Minor)
return true;
if (Major == version.Major && Minor == version.Minor && Patch > version.Patch)
return true;
return false;
}
public bool IsOlderThan(IVersion version)
{
if (Major < version.Major)
return true;
if (Major == version.Major && Minor < version.Minor)
return true;
if (Major == version.Major && Minor == version.Minor && Patch < version.Patch)
return true;
return false;
}
public bool IsEqualTo(IVersion version)
{
return Major == version.Major && Minor == version.Minor && Patch == version.Patch;
}
public string ToShortString()
{
return $"{Major}.{Minor}.{Patch}";
}
public override string ToString()
{
return ToShortString();
}
}

View File

@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace DiscordBotCore.Online;
public class FileDownloader
{
private readonly HttpClient _httpClient;
public List<Task> ListOfDownloadTasks { get; private set; }
private bool IsParallelDownloader { get; set; }
public Action FinishAction { get; private set; }
public FileDownloader(bool isParallelDownloader)
{
_httpClient = new HttpClient();
ListOfDownloadTasks = new List<Task>();
IsParallelDownloader = isParallelDownloader;
}
public FileDownloader(bool isParallelDownloader, Action finishAction)
{
_httpClient = new HttpClient();
ListOfDownloadTasks = new List<Task>();
IsParallelDownloader = isParallelDownloader;
FinishAction = finishAction;
}
public async Task StartDownloadTasks()
{
if (IsParallelDownloader)
{
await Task.WhenAll(ListOfDownloadTasks);
}
else
{
foreach (var task in ListOfDownloadTasks)
{
await task;
}
}
FinishAction?.Invoke();
}
public void AppendDownloadTask(string downloadLink, string downloadLocation, IProgress<float> progress)
{
ListOfDownloadTasks.Add(CreateDownloadTask(_httpClient, downloadLink, downloadLocation, progress));
}
public void AppendDownloadTask(string downloadLink, string downloadLocation, Action<float> progressCallback)
{
ListOfDownloadTasks.Add(CreateDownloadTask(_httpClient, downloadLink, downloadLocation, new Progress<float>(progressCallback)));
}
public static async Task CreateDownloadTask(HttpClient client, string url, string targetPath, IProgress<float> progress)
{
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
var totalBytes = response.Content.Headers.ContentLength ?? -1L;
var receivedBytes = 0L;
var targetDirectory = Path.GetDirectoryName(targetPath);
if (!string.IsNullOrEmpty(targetDirectory))
{
Directory.CreateDirectory(targetDirectory);
}
using var contentStream = await response.Content.ReadAsStreamAsync();
using var fileStream = new FileStream(targetPath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true);
var buffer = new byte[8192];
int bytesRead;
while ((bytesRead = await contentStream.ReadAsync(buffer)) > 0)
{
await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead));
receivedBytes += bytesRead;
if (totalBytes > 0)
{
float calculatedProgress = (float)receivedBytes / totalBytes;
progress.Report(calculatedProgress);
}
}
progress.Report(1f);
}
}

View File

@@ -107,21 +107,4 @@ internal static class OnlineFunctions
using var client = new HttpClient();
return await client.GetStringAsync(url, cancellation);
}
internal static async Task<bool> IsInternetConnected()
{
bool result = false;
try
{
using var client = new HttpClient();
await client.GetStringAsync("files.wizzy-server.ro");
result = true;
}
catch
{
result = false;
}
return result;
}
}

View File

@@ -0,0 +1,89 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using DiscordBotCore.Interfaces.PluginManagement;
using DiscordBotCore.Others;
using DiscordBotCore.Plugin;
namespace DiscordBotCore.Online.Helpers;
internal class PluginRepository : IPluginRepository
{
private readonly IPluginRepositoryConfiguration _pluginRepositoryConfiguration;
private readonly HttpClient _httpClient;
internal PluginRepository(IPluginRepositoryConfiguration pluginRepositoryConfiguration)
{
_pluginRepositoryConfiguration = pluginRepositoryConfiguration;
_httpClient = new HttpClient();
_httpClient.BaseAddress = new System.Uri(_pluginRepositoryConfiguration.BaseUrl);
}
public async Task<List<OnlinePlugin>> GetAllPlugins()
{
HttpResponseMessage response =
await _httpClient.GetAsync(_pluginRepositoryConfiguration.PluginRepositoryLocation + "get-all-plugins");
if (!response.IsSuccessStatusCode)
{
Application.Log("Failed to get all plugins from the repository", LogType.Warning);
return [];
}
string content = await response.Content.ReadAsStringAsync();
List<OnlinePlugin> plugins = await JsonManager.ConvertFromJson<List<OnlinePlugin>>(content);
return plugins;
}
public async Task<OnlinePlugin?> GetPluginById(int pluginId)
{
HttpResponseMessage response =
await _httpClient.GetAsync(_pluginRepositoryConfiguration.PluginRepositoryLocation +
$"get-plugin/{pluginId}");
if (!response.IsSuccessStatusCode)
{
Application.Log("Failed to get plugin from the repository", LogType.Warning);
return null;
}
string content = await response.Content.ReadAsStringAsync();
OnlinePlugin plugin = await JsonManager.ConvertFromJson<OnlinePlugin>(content);
return plugin;
}
public async Task<OnlinePlugin?> GetPluginByName(string pluginName)
{
HttpResponseMessage response =
await _httpClient.GetAsync(_pluginRepositoryConfiguration.PluginRepositoryLocation +
$"get-plugin-by-name/{pluginName}");
if (!response.IsSuccessStatusCode)
{
Application.Log("Failed to get plugin from the repository", LogType.Warning);
return null;
}
string content = await response.Content.ReadAsStringAsync();
OnlinePlugin plugin = await JsonManager.ConvertFromJson<OnlinePlugin>(content);
return plugin;
}
public async Task<List<OnlineDependencyInfo>> GetDependenciesForPlugin(int pluginId)
{
HttpResponseMessage response = await _httpClient.GetAsync(_pluginRepositoryConfiguration.DependenciesRepositoryLocation + $"get-dependencies-for-plugin/{pluginId}");
if(!response.IsSuccessStatusCode)
{
Application.Log("Failed to get dependencies for plugin from the repository", LogType.Warning);
return [];
}
string content = await response.Content.ReadAsStringAsync();
List<OnlineDependencyInfo> dependencies = await JsonManager.ConvertFromJson<List<OnlineDependencyInfo>>(content);
return dependencies;
}
}

View File

@@ -0,0 +1,19 @@
using DiscordBotCore.Interfaces.PluginManagement;
namespace DiscordBotCore.Online.Helpers;
public class PluginRepositoryConfiguration : IPluginRepositoryConfiguration
{
public static PluginRepositoryConfiguration Default => new ("http://localhost:5097/api/v1/", "plugins-repository/", "dependencies-repository/");
public string BaseUrl { get; }
public string PluginRepositoryLocation { get; }
public string DependenciesRepositoryLocation { get; }
public PluginRepositoryConfiguration(string baseUrl, string pluginRepositoryLocation, string dependenciesRepositoryLocation)
{
BaseUrl = baseUrl;
PluginRepositoryLocation = pluginRepositoryLocation;
DependenciesRepositoryLocation = dependenciesRepositoryLocation;
}
}

View File

@@ -1,20 +0,0 @@
using System.Text.Json.Serialization;
using DiscordBotCore.Interfaces.Updater;
namespace DiscordBotCore.Online.Helpers;
public class PluginVersion: Version
{
[JsonConstructor]
public PluginVersion(int major, int minor, int patch): base(major, minor, patch)
{
}
public PluginVersion(string versionAsString): base(versionAsString)
{
}
public override string ToString()
{
return ToShortString();
}
}

View File

@@ -3,56 +3,54 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using DiscordBotCore.Interfaces.PluginManagement;
using DiscordBotCore.Others;
using DiscordBotCore.Plugin;
using DiscordBotCore.Repository;
using DiscordBotCore.Updater.Plugins;
namespace DiscordBotCore.Online;
public sealed class PluginManager
{
private static readonly string _LibrariesBaseFolder = "Libraries";
private readonly PluginRepository _PluginRepository;
private readonly IPluginRepository _PluginRepository;
internal InstallingPluginInformation? InstallingPluginInformation { get; private set; }
public PluginManager(PluginRepository pluginRepository)
internal PluginManager(IPluginRepository pluginRepository)
{
_PluginRepository = pluginRepository;
}
public async Task<List<PluginOnlineInfo>> GetPluginsList()
public async Task<List<OnlinePlugin>> GetPluginsList()
{
var jsonText = await _PluginRepository.JsonGetAllPlugins();
List<PluginOnlineInfo> result = await JsonManager.ConvertFromJson<List<PluginOnlineInfo>>(jsonText);
var currentOs = OperatingSystem.IsWindows() ? OSType.WINDOWS :
OperatingSystem.IsLinux() ? OSType.LINUX :
OperatingSystem.IsMacOS() ? OSType.MACOSX : OSType.NONE;
return result.FindAll(pl => (pl.SupportedOS & currentOs) != 0);
}
public async Task<PluginOnlineInfo?> GetPluginDataByName(string pluginName)
{
List<PluginOnlineInfo>? plugins = await GetPluginsList();
if (plugins is null)
var onlinePlugins = await _PluginRepository.GetAllPlugins();
if (!onlinePlugins.Any())
{
return null;
Application.Log("Failed to get all plugins from the repository", LogType.Warning);
return [];
}
PluginOnlineInfo? result = plugins.Find(pl => pl.Name.Contains(pluginName, StringComparison.CurrentCultureIgnoreCase));
if (result is null)
int os = OS.GetOperatingSystemInt();
var response = onlinePlugins.Where(plugin => plugin.OperatingSystem == os).ToList();
return response;
}
public async Task<OnlinePlugin?> GetPluginDataByName(string pluginName)
{
var plugin = await _PluginRepository.GetPluginByName(pluginName);
if (plugin == null)
{
Application.Log("Failed to get plugin from the repository", LogType.Warning);
return null;
}
return result;
return plugin;
}
public async Task RemovePluginFromDatabase(string pluginName)
private async Task RemovePluginFromDatabase(string pluginName)
{
List<PluginInfo> installedPlugins = await JsonManager.ConvertFromJson<List<PluginInfo>>(await File.ReadAllTextAsync(Application.CurrentApplication.PluginDatabase));
@@ -60,24 +58,6 @@ public sealed class PluginManager
await JsonManager.SaveToJsonFile(Application.CurrentApplication.PluginDatabase, installedPlugins);
}
public async Task ExecutePluginInstallScripts(List<OnlineScriptDependencyInfo> listOfDependencies)
{
string? console = Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("console.terminal");
if (string.IsNullOrEmpty(console))
{
return;
}
string? cmd_prefix = Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("console.cmd_prefix");
if (string.IsNullOrEmpty(cmd_prefix))
{
return;
}
foreach (var script in listOfDependencies)
await ServerCom.RunConsoleCommand(console, $"{cmd_prefix}{script.ScriptContent}");
}
public async Task AppendPluginToDatabase(PluginInfo pluginData)
{
List<PluginInfo> installedPlugins = await JsonManager.ConvertFromJson<List<PluginInfo>>(await File.ReadAllTextAsync(Application.CurrentApplication.PluginDatabase));
@@ -102,26 +82,10 @@ public sealed class PluginManager
return installedPlugins.Any(plugin => plugin.PluginName == pluginName);
}
public async Task CheckForUpdates()
{
var pluginUpdater = new PluginUpdater(this);
List<PluginInfo> installedPlugins = await GetInstalledPlugins();
foreach (var plugin in installedPlugins)
{
if (await pluginUpdater.HasUpdate(plugin.PluginName))
{
Application.CurrentApplication.Logger.Log("Updating plugin: " + plugin.PluginName, this, LogType.Info);
await pluginUpdater.UpdatePlugin(plugin.PluginName);
}
}
}
public async Task<bool> MarkPluginToUninstall(string pluginName)
{
IEnumerable<PluginInfo> installedPlugins = await GetInstalledPlugins();
IEnumerable<PluginInfo> info = installedPlugins.Where(info => info.PluginName == pluginName).AsEnumerable();
List<PluginInfo> installedPlugins = await GetInstalledPlugins();
List<PluginInfo> info = installedPlugins.Where(info => info.PluginName == pluginName).ToList();
if (!info.Any())
return false;
@@ -132,10 +96,8 @@ public sealed class PluginManager
item.IsMarkedToUninstall = true;
await AppendPluginToDatabase(item);
}
return true;
}
public async Task UninstallMarkedPlugins()
@@ -201,137 +163,54 @@ public sealed class PluginManager
return relative;
}
public async Task InstallPluginWithNoProgress(PluginOnlineInfo pluginData)
public async Task InstallPluginNoProgress(OnlinePlugin plugin)
{
InstallingPluginInformation = new InstallingPluginInformation() { PluginName = pluginData.Name };
// Calculate the total number of steps: main file + dependencies
int totalSteps = pluginData.HasFileDependencies ? pluginData.Dependencies.Count + 1 : 1;
// Each step contributes this percentage to the total progress
InstallingPluginInformation = new InstallingPluginInformation() { PluginName = plugin.PluginName };
List<OnlineDependencyInfo> dependencies = await _PluginRepository.GetDependenciesForPlugin(plugin.PluginId);
int totalSteps = dependencies.Count + 1;
float stepFraction = 100f / totalSteps;
// Tracks the current cumulative progress in percentage
float currentProgress = 0f;
InstallingPluginInformation.IsInstalling = true;
// Create a progress updater that maps the file's 0100 progress to its portion of the total progress
var progress = currentProgress;
IProgress<float> downloadProgress = new Progress<float>(fileProgress =>
{
// Map the file progress (0-100) to the total progress
InstallingPluginInformation.InstallationProgress = currentProgress + (fileProgress / 100f) * stepFraction;
InstallingPluginInformation.InstallationProgress = progress + (fileProgress / 100f) * stepFraction;
});
// Download the main plugin file and map its progress
await ServerCom.DownloadFileAsync(pluginData.DownLoadLink,
$"{Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("PluginFolder")}/{pluginData.Name}.dll",
downloadProgress
);
// Update cumulative progress after the main file
await ServerCom.DownloadFileAsync(plugin.PluginLink,
$"{Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("PluginFolder")}/{plugin.PluginName}.dll",
downloadProgress);
currentProgress += stepFraction;
// Download file dependencies if they exist
if (pluginData.HasFileDependencies)
if (dependencies.Count > 0)
{
foreach (var dependency in pluginData.Dependencies)
foreach (var dependency in dependencies)
{
string dependencyLocation =
GenerateDependencyRelativePath(pluginData.Name, dependency.DownloadLocation);
// Download dependency and map its progress
string dependencyLocation = GenerateDependencyRelativePath(plugin.PluginName, dependency.DownloadLocation);
await ServerCom.DownloadFileAsync(dependency.DownloadLink, dependencyLocation, downloadProgress);
// Update cumulative progress after each dependency
currentProgress += stepFraction;
}
}
// Run script dependencies if any (doesn't affect download progress percentage)
if (pluginData.HasScriptDependencies)
{
foreach (var scriptDependency in pluginData.ScriptDependencies)
{
string? console = Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("console.terminal");
if (string.IsNullOrEmpty(console))
{
return;
}
string? cmdPrefix = Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("console.cmd_prefix");
if (string.IsNullOrEmpty(cmdPrefix))
{
return;
}
string arguments = $"{cmdPrefix}{scriptDependency.ScriptContent}";
await ServerCom.RunConsoleCommand(console, arguments);
}
}
// Register the plugin in the database
PluginInfo pluginInfo = PluginInfo.FromOnlineInfo(pluginData);
await AppendPluginToDatabase(pluginInfo);
InstallingPluginInformation.IsInstalling = false; // Mark installation as complete
}
public async Task InstallPluginWithProgressBar(PluginOnlineInfo pluginData, IProgress<float>? installProgress)
{
installProgress?.Report(0f);
int totalSteps = pluginData.HasFileDependencies ? pluginData.Dependencies.Count + 1 : 1;
float stepProgress = 1f / totalSteps;
float currentProgress = 0f;
IProgress<float> progress = new Progress<float>((p) =>
PluginInfo pluginInfo = PluginInfo.FromOnlineInfo(plugin, dependencies);
await AppendPluginToDatabase(pluginInfo);
InstallingPluginInformation.IsInstalling = false;
}
public async Task<Tuple<Dictionary<string, string>, List<OnlineDependencyInfo>>> GatherInstallDataForPlugin(OnlinePlugin plugin)
{
List<OnlineDependencyInfo> dependencies = await _PluginRepository.GetDependenciesForPlugin(plugin.PluginId);
var downloads = new Dictionary<string, string> { { $"{Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("PluginFolder")}/{plugin.PluginName}.dll", plugin.PluginLink } };
foreach(var dependency in dependencies)
{
installProgress?.Report(currentProgress + stepProgress * p);
});
await ServerCom.DownloadFileAsync(pluginData.DownLoadLink, $"{Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("PluginFolder")}/{pluginData.Name}.dll", progress);
if (pluginData.HasFileDependencies)
foreach (var dependency in pluginData.Dependencies)
{
string dependencyLocation = GenerateDependencyRelativePath(pluginData.Name, dependency.DownloadLocation);
await ServerCom.DownloadFileAsync(dependency.DownloadLink, dependencyLocation, progress);
currentProgress += stepProgress;
}
if (pluginData.HasScriptDependencies)
{
foreach (var scriptDependency in pluginData.ScriptDependencies)
{
string? console = Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("console.terminal");
if (string.IsNullOrEmpty(console))
{
return;
}
string? cmdPrefix = Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("console.cmd_prefix");
if (string.IsNullOrEmpty(cmdPrefix))
{
return;
}
string arguments = $"{cmdPrefix}{scriptDependency.ScriptContent}";
await ServerCom.RunConsoleCommand(console, arguments);
}
string dependencyLocation = GenerateDependencyRelativePath(plugin.PluginName, dependency.DownloadLocation);
downloads.Add(dependencyLocation, dependency.DownloadLink);
}
PluginInfo pluginInfo = PluginInfo.FromOnlineInfo(pluginData);
await AppendPluginToDatabase(pluginInfo);
return (downloads, dependencies).ToTuple();
}
public async Task SetEnabledStatus(string pluginName, bool status)

View File

@@ -1,48 +1,17 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using DiscordBotCore.Online.Helpers;
using DiscordBotCore.Others;
namespace DiscordBotCore.Online;
public static class ServerCom
{
/// <summary>
/// Read all lines from a file async
/// </summary>
/// <param name="link">The link of the file</param>
/// <returns></returns>
public static async Task<List<string>> ReadTextFromURL(string link)
{
var response = await OnlineFunctions.DownloadStringAsync(link);
var lines = response.Split('\n');
return lines.ToList();
}
/// <summary>
/// Get all text from a file async
/// </summary>
/// <param name="link">The link of the file</param>
/// <returns></returns>
public static async Task<string> GetAllTextFromUrl(string link)
{
var response = await OnlineFunctions.DownloadStringAsync(link);
return response;
}
/// <summary>
/// Download file from url
/// </summary>
/// <param name="URL">The url to the file</param>
/// <param name="location">The location where to store the downloaded data</param>
/// <param name="progress">The <see cref="IProgress{T}" /> to track the download</param>
/// <returns></returns>
public static async Task DownloadFileAsync(
private static async Task DownloadFileAsync(
string URL, string location, IProgress<float>? progress,
IProgress<long>? downloadedBytes)
{
@@ -60,34 +29,8 @@ public static class ServerCom
}
}
public static async Task DownloadFileAsync(string URl, string location, IProgress<float> progress)
public static async Task DownloadFileAsync(string url, string location, IProgress<float> progress)
{
await DownloadFileAsync(URl, location, progress, null);
await DownloadFileAsync(url, location, progress, null);
}
public static async Task DownloadFileAsync(string url, string location)
{
await DownloadFileAsync(url, location, null, null);
}
public static Task CreateDownloadTask(string URl, string location)
{
return DownloadFileAsync(URl, location, null, null);
}
public static Task CreateDownloadTask(string URl, string location, IProgress<float> progress)
{
return DownloadFileAsync(URl, location, progress, null);
}
public static async Task RunConsoleCommand(string console, string command)
{
Process process = new();
process.StartInfo.FileName = console;
process.StartInfo.Arguments = command;
process.Start();
await process.WaitForExitAsync();
}
}

View File

@@ -26,15 +26,6 @@ public enum InternalActionRunType
OnStartupAndCall
}
[Flags]
public enum OSType: byte
{
NONE = 0,
WINDOWS = 1 << 0,
LINUX = 2 << 1,
MACOSX = 3 << 2
}
public enum PluginType
{
UNKNOWN,

View File

@@ -93,7 +93,12 @@ public static class JsonManager
text.Position = 0;
var obj = await JsonSerializer.DeserializeAsync<T>(text);
JsonSerializerOptions options = new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true
};
var obj = await JsonSerializer.DeserializeAsync<T>(text, options);
await text.FlushAsync();
text.Close();

View File

@@ -0,0 +1,48 @@
using System;
namespace DiscordBotCore.Others;
public class OS
{
public enum OperatingSystem : int
{
Windows = 0,
Linux = 1,
MacOS = 2
}
public static OperatingSystem GetOperatingSystem()
{
if(System.OperatingSystem.IsLinux()) return OperatingSystem.Linux;
if(System.OperatingSystem.IsWindows()) return OperatingSystem.Windows;
if(System.OperatingSystem.IsMacOS()) return OperatingSystem.MacOS;
throw new PlatformNotSupportedException();
}
public static string GetOperatingSystemString(OperatingSystem os)
{
return os switch
{
OperatingSystem.Windows => "Windows",
OperatingSystem.Linux => "Linux",
OperatingSystem.MacOS => "MacOS",
_ => throw new ArgumentOutOfRangeException()
};
}
public static OperatingSystem GetOperatingSystemFromString(string os)
{
return os.ToLower() switch
{
"windows" => OperatingSystem.Windows,
"linux" => OperatingSystem.Linux,
"macos" => OperatingSystem.MacOS,
_ => throw new ArgumentOutOfRangeException()
};
}
public static int GetOperatingSystemInt()
{
return (int) GetOperatingSystem();
}
}

View File

@@ -6,7 +6,9 @@ namespace DiscordBotCore.Plugin;
public class OnlineDependencyInfo
{
public string DependencyName { get; private set; }
[JsonPropertyName("dependencyLink")]
public string DownloadLink { get; private set; }
[JsonPropertyName("dependencyLocation")]
public string DownloadLocation { get; private set; }
public bool IsExecutable { get; private set; }

View File

@@ -0,0 +1,27 @@
using System.Text.Json.Serialization;
namespace DiscordBotCore.Plugin;
public class OnlinePlugin
{
public int PluginId { get; private set; }
public string PluginName { get; private set; }
public string PluginDescription { get; private set; }
public string LatestVersion { get; private set; }
public string PluginAuthor { get; private set; }
public string PluginLink { get; private set; }
public int OperatingSystem { get; private set; }
[JsonConstructor]
public OnlinePlugin(int pluginId, string pluginName, string pluginDescription, string latestVersion,
string pluginAuthor, string pluginLink, int operatingSystem)
{
PluginId = pluginId;
PluginName = pluginName;
PluginDescription = pluginDescription;
LatestVersion = latestVersion;
PluginAuthor = pluginAuthor;
PluginLink = pluginLink;
OperatingSystem = operatingSystem;
}
}

View File

@@ -1,14 +0,0 @@
namespace DiscordBotCore.Plugin
{
public class OnlineScriptDependencyInfo
{
public string DependencyName { get; private set; }
public string ScriptContent { get; private set; }
public OnlineScriptDependencyInfo(string dependencyName, string scriptContent)
{
DependencyName = dependencyName;
ScriptContent = scriptContent;
}
}
}

View File

@@ -8,7 +8,7 @@ namespace DiscordBotCore.Plugin;
public class PluginInfo
{
public string PluginName { get; private set; }
public PluginVersion PluginVersion { get; private set; }
public string PluginVersion { get; private set; }
public string FilePath { get; private set; }
public Dictionary<string, string> ListOfExecutableDependencies {get; private set;}
public bool IsMarkedToUninstall {get; internal set;}
@@ -16,7 +16,7 @@ public class PluginInfo
public bool IsEnabled { get; internal set; }
[JsonConstructor]
public PluginInfo(string pluginName, PluginVersion pluginVersion, Dictionary<string, string> listOfExecutableDependencies, bool isMarkedToUninstall, bool isOfflineAdded, bool isEnabled)
public PluginInfo(string pluginName, string pluginVersion, Dictionary<string, string> listOfExecutableDependencies, bool isMarkedToUninstall, bool isOfflineAdded, bool isEnabled)
{
PluginName = pluginName;
PluginVersion = pluginVersion;
@@ -27,7 +27,7 @@ public class PluginInfo
IsEnabled = isEnabled;
}
public PluginInfo(string pluginName, PluginVersion pluginVersion, Dictionary<string, string> listOfExecutableDependencies)
public PluginInfo(string pluginName, string pluginVersion, Dictionary<string, string> listOfExecutableDependencies)
{
PluginName = pluginName;
PluginVersion = pluginVersion;
@@ -38,21 +38,14 @@ public class PluginInfo
IsEnabled = true;
}
public static PluginInfo FromOnlineInfo(PluginOnlineInfo onlineInfo)
public static PluginInfo FromOnlineInfo(OnlinePlugin plugin, List<OnlineDependencyInfo> dependencies)
{
var pluginName = onlineInfo.Name;
var version = onlineInfo.Version;
var dependencies= onlineInfo.Dependencies;
if(dependencies is null)
{
return new PluginInfo(pluginName, version, new Dictionary<string, string>());
}
var executableDependencies = dependencies.Where(dep => dep.IsExecutable);
var dictDependencies = executableDependencies.Select(dep => new KeyValuePair<string, string>(dep.DependencyName, dep.DownloadLocation)).ToDictionary();
return new PluginInfo(pluginName, version, dictDependencies);
PluginInfo pluginInfo = new PluginInfo(
plugin.PluginName, plugin.LatestVersion,
dependencies.Where(dependency => dependency.IsExecutable)
.ToDictionary(dependency => dependency.DependencyName, dependency => dependency.DownloadLocation)
);
return pluginInfo;
}
}

View File

@@ -1,56 +0,0 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using DiscordBotCore.Online.Helpers;
using DiscordBotCore.Others;
namespace DiscordBotCore.Plugin;
public class PluginOnlineInfo
{
public string Name { get; private set; }
public string Author { get; private set; }
public PluginVersion Version { get; private set; }
public string DownLoadLink { get; private set; }
public string Description { get; private set; }
public List<OnlineDependencyInfo>? Dependencies { get; private set; }
public List<OnlineScriptDependencyInfo>? ScriptDependencies { get; private set; }
public OSType SupportedOS { get; private set; }
public bool HasFileDependencies => Dependencies is not null && Dependencies.Count > 0;
public bool HasScriptDependencies => ScriptDependencies is not null && ScriptDependencies.Count > 0;
[JsonConstructor]
public PluginOnlineInfo(string name, string author, PluginVersion version, string description, string downLoadLink, OSType supportedOS, List<OnlineDependencyInfo> dependencies, List<OnlineScriptDependencyInfo> scriptDependencies)
{
Name = name;
Author = author;
Version = version;
Description = description;
DownLoadLink = downLoadLink;
SupportedOS = supportedOS;
Dependencies = dependencies;
ScriptDependencies = scriptDependencies;
}
public PluginOnlineInfo(string name, string author, PluginVersion version, string description, string downLoadLink, OSType supportedOS)
{
Name = name;
Author = author;
Version = version;
Description = description;
DownLoadLink = downLoadLink;
SupportedOS = supportedOS;
Dependencies = new List<OnlineDependencyInfo>();
ScriptDependencies = new List<OnlineScriptDependencyInfo>();
}
public static async Task<PluginOnlineInfo> FromRawData(string jsonText)
{
return await JsonManager.ConvertFromJson<PluginOnlineInfo>(jsonText);
}
public override string ToString()
{
return $"{Name} <{Author}> - {Version} ({Description})";
}
}

View File

@@ -1,51 +0,0 @@
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://files.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.CurrentApplication.Logger.LogException(ex, Application.CurrentApplication);
return Default;
}
}
}

View File

@@ -1,33 +0,0 @@
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

@@ -1,127 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using DiscordBotCore.Interfaces.Updater;
using DiscordBotCore.Online;
using DiscordBotCore.Others;
namespace DiscordBotCore.Updater.Application
{
public class AppUpdater
{
const string ProjectName = "SethDiscordBot";
private static readonly string _DefaultUpdateUrl = $"https://github.com/andreitdr/{ProjectName}/releases/latest";
private static readonly string _DefaultUpdateDownloadUrl = $"https://github.com/andreitdr/{ProjectName}/releases/download/v";
private static readonly string _WindowsUpdateFile = "win-x64.zip";
private static readonly string _LinuxUpdateFile = "linux-x64.zip";
private static readonly string _MacOSUpdateFile = "osx-x64.zip";
private static readonly string _TempUpdateFolder = "temp";
private async Task<AppVersion> GetOnlineVersion()
{
HttpClient client = new HttpClient();
var response = await client.GetAsync(_DefaultUpdateUrl);
if (!response.IsSuccessStatusCode)
return AppVersion.CurrentAppVersion;
var url = response.RequestMessage.RequestUri.ToString();
var version = url.Split('/')[^1].Substring(1); // Remove the 'v' from the version number
return new AppVersion(version);
}
private string GetDownloadUrl(AppVersion version)
{
string downloadUrl = _DefaultUpdateDownloadUrl;
downloadUrl += $"{version.ToShortString()}/";
if(OperatingSystem.IsWindows())
downloadUrl += _WindowsUpdateFile;
else if (OperatingSystem.IsLinux())
downloadUrl += _LinuxUpdateFile;
else if (OperatingSystem.IsMacOS())
downloadUrl += _MacOSUpdateFile;
else
throw new PlatformNotSupportedException("Unsupported operating system");
return downloadUrl;
}
public async Task<Update?> PrepareUpdate()
{
AppVersion currentVersion = AppVersion.CurrentAppVersion;
AppVersion newVersion = await GetOnlineVersion();
if(!newVersion.IsNewerThan(currentVersion))
return null;
string downloadUrl = GetDownloadUrl(newVersion);
Update update = new Update(currentVersion, newVersion, downloadUrl);
return update;
}
private void PrepareCurrentFolderForOverwrite()
{
List<string> files = new List<string>();
files.AddRange(Directory.GetFiles("./"));
foreach (var file in files)
{
File.Move(file, file + ".bak");
}
}
private async Task CopyFolderContentOverCurrentFolder(string current, string otherFolder)
{
var files = Directory.GetFiles(otherFolder, "*.*", SearchOption.AllDirectories);
foreach (var file in files)
{
string relativePath = file.Replace(otherFolder, "");
string newPath = current + relativePath;
Directory.CreateDirectory(Path.GetDirectoryName(newPath));
File.Copy(file, newPath);
}
}
public async Task SelfUpdate(Update update, IProgress<float> progress)
{
Directory.CreateDirectory(_TempUpdateFolder);
string tempFile = $"./{_TempUpdateFolder}/update.zip";
string tempFolder = $"./{_TempUpdateFolder}/";
Directory.CreateDirectory(tempFolder);
await ServerCom.DownloadFileAsync(update.UpdateUrl, tempFile, progress);
await ArchiveManager.ExtractArchive(tempFile, tempFolder, progress, UnzipProgressType.PERCENTAGE_FROM_TOTAL_SIZE);
PrepareCurrentFolderForOverwrite();
if (OperatingSystem.IsWindows())
tempFolder += _WindowsUpdateFile;
else if (OperatingSystem.IsLinux())
tempFolder += _LinuxUpdateFile;
else if (OperatingSystem.IsMacOS())
tempFolder += _MacOSUpdateFile;
else throw new PlatformNotSupportedException();
await CopyFolderContentOverCurrentFolder("./", tempFolder.Substring(0, tempFolder.Length-4)); // Remove the .zip from the folder name
Process.Start("DiscordBot", "--update-cleanup");
Environment.Exit(0);
}
}
}

View File

@@ -1,19 +0,0 @@
using DiscordBotCore.Interfaces.Updater;
namespace DiscordBotCore.Updater.Application
{
public class Update
{
public AppVersion NewVersion { get; private set; }
public AppVersion CurrentVersion { get; private set; }
public string UpdateUrl { get; private set; }
public Update(AppVersion currentVersion, AppVersion updateVersion, string updateUrl)
{
NewVersion = updateVersion;
CurrentVersion = currentVersion;
UpdateUrl = updateUrl;
}
}
}

View File

@@ -1,65 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using DiscordBotCore.Online;
using DiscordBotCore.Others;
using DiscordBotCore.Others.Exceptions;
using DiscordBotCore.Plugin;
namespace DiscordBotCore.Updater.Plugins;
public class PluginUpdater
{
private readonly PluginManager _PluginsManager;
public PluginUpdater(PluginManager pluginManager)
{
_PluginsManager = pluginManager;
}
private async Task<PluginOnlineInfo> GetPluginInfo(string pluginName)
{
var result = await _PluginsManager.GetPluginDataByName(pluginName);
if(result is null)
throw new PluginNotFoundException(pluginName);
return result;
}
private async Task<PluginInfo> GetLocalPluginInfo(string pluginName)
{
string pluginsDatabase = File.ReadAllText(DiscordBotCore.Application.CurrentApplication.PluginDatabase);
List<PluginInfo> installedPlugins = await JsonManager.ConvertFromJson<List<PluginInfo>>(pluginsDatabase);
var result = installedPlugins.Find(p => p.PluginName == pluginName);
if (result is null)
throw new PluginNotFoundException(pluginName);
return result;
}
public async Task UpdatePlugin(string pluginName, IProgress<float>? progressMeter = null)
{
PluginOnlineInfo pluginInfo = await GetPluginInfo(pluginName);
await ServerCom.DownloadFileAsync(pluginInfo.DownLoadLink, $"{DiscordBotCore.Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("PluginFolder")}/{pluginName}.dll", 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));
}
public async Task<bool> HasUpdate(string pluginName)
{
var localPluginInfo = await GetLocalPluginInfo(pluginName);
if(localPluginInfo.IsOfflineAdded)
return false;
var pluginInfo = await GetPluginInfo(pluginName);
return pluginInfo.Version.IsNewerThan(localPluginInfo.PluginVersion);
}
}