From 72ea91d5aab8264938cc986cc095cac36b609cff Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 1 Jun 2025 20:48:49 +0200 Subject: [PATCH] Add EnterpriseEnvironmentService and plugin management --- app/MindWork AI Studio/Program.cs | 1 + .../PluginSystem/PluginFactory.Download.cs | 56 ++++++++++++++ .../PluginSystem/PluginFactory.Remove.cs | 54 +++++++++++++ .../Tools/PluginSystem/PluginFactory.cs | 2 + .../Services/EnterpriseEnvironmentService.cs | 75 +++++++++++++++++++ 5 files changed, 188 insertions(+) create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Download.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Remove.cs create mode 100644 app/MindWork AI Studio/Tools/Services/EnterpriseEnvironmentService.cs diff --git a/app/MindWork AI Studio/Program.cs b/app/MindWork AI Studio/Program.cs index 04247f56..fb7654ea 100644 --- a/app/MindWork AI Studio/Program.cs +++ b/app/MindWork AI Studio/Program.cs @@ -133,6 +133,7 @@ internal sealed class Program builder.Services.AddTransient(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); + builder.Services.AddHostedService(); builder.Services.AddRazorComponents() .AddInteractiveServerComponents() .AddHubOptions(options => diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Download.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Download.cs new file mode 100644 index 00000000..30482b6e --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Download.cs @@ -0,0 +1,56 @@ +using System.IO.Compression; + +namespace AIStudio.Tools.PluginSystem; + +public static partial class PluginFactory +{ + public static async Task TryDownloadingConfigPluginAsync(Guid configPlugId, string configServerUrl, CancellationToken cancellationToken = default) + { + if (!IS_INITIALIZED) + return false; + + LOG.LogInformation($"Downloading configuration plugin with ID: {configPlugId} from server: {configServerUrl}"); + var tempDownloadFile = Path.GetTempFileName(); + try + { + using var httpClient = new HttpClient(); + var response = await httpClient.GetAsync($"{configServerUrl}/{configPlugId}.zip", cancellationToken); + if (response.IsSuccessStatusCode) + { + await using var tempFileStream = File.Create(tempDownloadFile); + await response.Content.CopyToAsync(tempFileStream, cancellationToken); + + var pluginDirectory = Path.Join(CONFIGURATION_PLUGINS_ROOT, configPlugId.ToString()); + if(Directory.Exists(pluginDirectory)) + Directory.Delete(pluginDirectory, true); + + Directory.CreateDirectory(pluginDirectory); + ZipFile.ExtractToDirectory(tempDownloadFile, pluginDirectory); + + LOG.LogInformation($"Configuration plugin with ID='{configPlugId}' downloaded and extracted successfully to '{pluginDirectory}'."); + } + else + LOG.LogError($"Failed to download the enterprise configuration plugin. HTTP Status: {response.StatusCode}"); + } + catch (Exception e) + { + LOG.LogError(e, "An error occurred while downloading or extracting the enterprise configuration plugin."); + } + finally + { + if (File.Exists(tempDownloadFile)) + { + try + { + File.Delete(tempDownloadFile); + } + catch (Exception e) + { + LOG.LogError(e, "Failed to delete the temporary download file."); + } + } + } + + return true; + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Remove.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Remove.cs new file mode 100644 index 00000000..9fa82a66 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Remove.cs @@ -0,0 +1,54 @@ +namespace AIStudio.Tools.PluginSystem; + +public static partial class PluginFactory +{ + public static void RemovePluginAsync(Guid pluginId) + { + if (!IS_INITIALIZED) + return; + + LOG.LogWarning($"Try to remove plugin with ID: {pluginId}"); + + // + // Remove the plugin from the available plugins list: + // + var availablePluginToRemove = AVAILABLE_PLUGINS.FirstOrDefault(p => p.Id == pluginId); + if (availablePluginToRemove == null) + { + LOG.LogWarning($"No plugin found with ID: {pluginId}"); + return; + } + + AVAILABLE_PLUGINS.Remove(availablePluginToRemove); + + // + // Remove the plugin from the running plugins list: + // + var runningPluginToRemove = RUNNING_PLUGINS.FirstOrDefault(p => p.Id == pluginId); + if (runningPluginToRemove == null) + LOG.LogWarning($"No running plugin found with ID: {pluginId}"); + else + RUNNING_PLUGINS.Remove(runningPluginToRemove); + + // + // Delete the plugin directory: + // + var pluginDirectory = Path.Join(CONFIGURATION_PLUGINS_ROOT, availablePluginToRemove.Id.ToString()); + if (Directory.Exists(pluginDirectory)) + { + try + { + Directory.Delete(pluginDirectory, true); + LOG.LogInformation($"Plugin directory '{pluginDirectory}' deleted successfully."); + } + catch (Exception ex) + { + LOG.LogError(ex, $"Failed to delete plugin directory '{pluginDirectory}'."); + } + } + else + LOG.LogWarning($"Plugin directory '{pluginDirectory}' does not exist."); + + LOG.LogInformation($"Plugin with ID: {pluginId} removed successfully."); + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs index f424a133..a5aaef37 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs @@ -12,6 +12,7 @@ public static partial class PluginFactory private static string DATA_DIR = string.Empty; private static string PLUGINS_ROOT = string.Empty; private static string INTERNAL_PLUGINS_ROOT = string.Empty; + private static string CONFIGURATION_PLUGINS_ROOT = string.Empty; private static FileSystemWatcher HOT_RELOAD_WATCHER = null!; private static ILanguagePlugin BASE_LANGUAGE_PLUGIN = NoPluginLanguage.INSTANCE; @@ -29,6 +30,7 @@ public static partial class PluginFactory DATA_DIR = SettingsManager.DataDirectory!; PLUGINS_ROOT = Path.Join(DATA_DIR, "plugins"); INTERNAL_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".internal"); + CONFIGURATION_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".config"); if (!Directory.Exists(PLUGINS_ROOT)) Directory.CreateDirectory(PLUGINS_ROOT); diff --git a/app/MindWork AI Studio/Tools/Services/EnterpriseEnvironmentService.cs b/app/MindWork AI Studio/Tools/Services/EnterpriseEnvironmentService.cs new file mode 100644 index 00000000..b0b480ec --- /dev/null +++ b/app/MindWork AI Studio/Tools/Services/EnterpriseEnvironmentService.cs @@ -0,0 +1,75 @@ +using AIStudio.Tools.PluginSystem; + +namespace AIStudio.Tools.Services; + +public sealed class EnterpriseEnvironmentService(ILogger logger, RustService rustService) : BackgroundService +{ + public static EnterpriseEnvironment CURRENT_ENVIRONMENT; + + private static readonly TimeSpan CHECK_INTERVAL = TimeSpan.FromMinutes(16); + + #region Overrides of BackgroundService + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + logger.LogInformation("The enterprise environment service was initialized."); + + await this.StartUpdating(); + while (!stoppingToken.IsCancellationRequested) + { + await Task.Delay(CHECK_INTERVAL, stoppingToken); + await this.StartUpdating(); + } + } + + #endregion + + private async Task StartUpdating() + { + try + { + logger.LogInformation("Starting update of the enterprise environment."); + + var enterpriseRemoveConfigId = await rustService.EnterpriseEnvRemoveConfigId(); + var isPlugin2RemoveInUse = PluginFactory.AvailablePlugins.Any(plugin => plugin.Id == enterpriseRemoveConfigId); + if (enterpriseRemoveConfigId != Guid.Empty && isPlugin2RemoveInUse) + { + logger.LogWarning($"The enterprise environment configuration ID '{enterpriseRemoveConfigId}' must be removed."); + PluginFactory.RemovePluginAsync(enterpriseRemoveConfigId); + } + + var enterpriseConfigServerUrl = await rustService.EnterpriseEnvConfigServerUrl(); + var enterpriseConfigId = await rustService.EnterpriseEnvConfigId(); + var nextEnterpriseEnvironment = new EnterpriseEnvironment(enterpriseConfigServerUrl, enterpriseConfigId); + if (CURRENT_ENVIRONMENT != nextEnterpriseEnvironment) + { + logger.LogInformation("The enterprise environment has changed. Updating the current environment."); + CURRENT_ENVIRONMENT = nextEnterpriseEnvironment; + + switch (enterpriseConfigServerUrl) + { + case null when enterpriseConfigId == Guid.Empty: + logger.LogInformation("AI Studio runs without an enterprise configuration."); + break; + + case null: + logger.LogWarning($"AI Studio runs with an enterprise configuration id ('{enterpriseConfigId}'), but the configuration server URL is not set."); + break; + + case not null when enterpriseConfigId == Guid.Empty: + logger.LogWarning($"AI Studio runs with an enterprise configuration server URL ('{enterpriseConfigServerUrl}'), but the configuration ID is not set."); + break; + + default: + logger.LogInformation($"AI Studio runs with an enterprise configuration id ('{enterpriseConfigId}') and configuration server URL ('{enterpriseConfigServerUrl}')."); + await PluginFactory.TryDownloadingConfigPluginAsync(enterpriseConfigId, enterpriseConfigServerUrl); + break; + } + } + } + catch (Exception e) + { + logger.LogError(e, "An error occurred while updating the enterprise environment."); + } + } +} \ No newline at end of file