2025-06-01 19:14:21 +00:00
using AIStudio.Tools.PluginSystem ;
namespace AIStudio.Tools.Services ;
2025-06-02 18:08:25 +00:00
public sealed class EnterpriseEnvironmentService ( ILogger < EnterpriseEnvironmentService > logger , RustService rustService ) : BackgroundService
2025-06-01 19:14:21 +00:00
{
2026-02-15 17:11:57 +00:00
public static List < EnterpriseEnvironment > CURRENT_ENVIRONMENTS = [ ] ;
2025-06-02 18:08:25 +00:00
#if DEBUG
private static readonly TimeSpan CHECK_INTERVAL = TimeSpan . FromMinutes ( 6 ) ;
#else
2025-06-01 19:14:21 +00:00
private static readonly TimeSpan CHECK_INTERVAL = TimeSpan . FromMinutes ( 16 ) ;
2025-06-02 18:08:25 +00:00
#endif
2025-06-01 19:14:21 +00:00
#region Overrides of BackgroundService
protected override async Task ExecuteAsync ( CancellationToken stoppingToken )
{
logger . LogInformation ( "The enterprise environment service was initialized." ) ;
2025-06-02 18:08:25 +00:00
await this . StartUpdating ( isFirstRun : true ) ;
2025-06-01 19:14:21 +00:00
while ( ! stoppingToken . IsCancellationRequested )
{
await Task . Delay ( CHECK_INTERVAL , stoppingToken ) ;
await this . StartUpdating ( ) ;
}
}
#endregion
2025-06-02 18:08:25 +00:00
private async Task StartUpdating ( bool isFirstRun = false )
2025-06-01 19:14:21 +00:00
{
try
{
2025-06-27 18:52:33 +00:00
logger . LogInformation ( "Start updating of the enterprise environment." ) ;
2026-02-15 17:11:57 +00:00
//
// Step 1: Handle deletions first.
//
List < Guid > deleteConfigIds ;
2026-01-24 19:17:35 +00:00
try
{
2026-02-15 17:11:57 +00:00
deleteConfigIds = await rustService . EnterpriseEnvDeleteConfigIds ( ) ;
2026-01-24 19:17:35 +00:00
}
catch ( Exception e )
{
2026-02-15 17:11:57 +00:00
logger . LogError ( e , "Failed to fetch the enterprise delete configuration IDs from the Rust service." ) ;
await MessageBus . INSTANCE . SendMessage ( null , Event . RUST_SERVICE_UNAVAILABLE , "EnterpriseEnvDeleteConfigIds failed" ) ;
2026-01-24 19:17:35 +00:00
return ;
}
2026-02-15 17:11:57 +00:00
foreach ( var deleteId in deleteConfigIds )
2025-06-01 19:14:21 +00:00
{
2026-02-15 17:11:57 +00:00
var isPluginInUse = PluginFactory . AvailablePlugins . Any ( plugin = > plugin . Id = = deleteId ) ;
if ( isPluginInUse )
{
logger . LogWarning ( "The enterprise environment configuration ID '{DeleteConfigId}' must be removed." , deleteId ) ;
PluginFactory . RemovePluginAsync ( deleteId ) ;
}
2025-06-01 19:14:21 +00:00
}
2026-01-24 19:17:35 +00:00
2026-02-15 17:11:57 +00:00
//
// Step 2: Fetch all active configurations.
//
List < EnterpriseEnvironment > fetchedConfigs ;
2026-01-24 19:17:35 +00:00
try
{
2026-02-15 17:11:57 +00:00
fetchedConfigs = await rustService . EnterpriseEnvConfigs ( ) ;
2026-01-24 19:17:35 +00:00
}
catch ( Exception e )
{
2026-02-15 17:11:57 +00:00
logger . LogError ( e , "Failed to fetch the enterprise configurations from the Rust service." ) ;
await MessageBus . INSTANCE . SendMessage ( null , Event . RUST_SERVICE_UNAVAILABLE , "EnterpriseEnvConfigs failed" ) ;
2026-01-24 19:17:35 +00:00
return ;
}
2026-02-15 17:11:57 +00:00
//
// Step 3: Determine ETags and build the next environment list.
//
var nextEnvironments = new List < EnterpriseEnvironment > ( ) ;
foreach ( var config in fetchedConfigs )
2026-01-24 19:17:35 +00:00
{
2026-02-15 17:11:57 +00:00
if ( ! config . IsActive )
{
logger . LogWarning ( "Skipping inactive enterprise configuration with ID '{ConfigId}'. There is either no valid server URL or config ID set." , config . ConfigurationId ) ;
continue ;
}
var etag = await PluginFactory . DetermineConfigPluginETagAsync ( config . ConfigurationId , config . ConfigurationServerUrl ) ;
nextEnvironments . Add ( config with { ETag = etag } ) ;
2026-01-24 19:17:35 +00:00
}
2026-02-15 17:11:57 +00:00
if ( nextEnvironments . Count = = 0 )
2026-01-24 19:17:35 +00:00
{
2026-02-15 17:11:57 +00:00
if ( CURRENT_ENVIRONMENTS . Count > 0 )
{
logger . LogWarning ( "AI Studio no longer has any enterprise configurations. Removing previously active configs." ) ;
// Remove plugins for configs that were previously active:
foreach ( var oldEnv in CURRENT_ENVIRONMENTS )
{
var isPluginInUse = PluginFactory . AvailablePlugins . Any ( plugin = > plugin . Id = = oldEnv . ConfigurationId ) ;
if ( isPluginInUse )
PluginFactory . RemovePluginAsync ( oldEnv . ConfigurationId ) ;
}
}
else
logger . LogInformation ( "AI Studio runs without any enterprise configurations." ) ;
CURRENT_ENVIRONMENTS = [ ] ;
2026-01-24 19:17:35 +00:00
return ;
}
2026-02-15 17:11:57 +00:00
//
// Step 4: Compare with current environments and process changes.
//
var currentIds = CURRENT_ENVIRONMENTS . Select ( e = > e . ConfigurationId ) . ToHashSet ( ) ;
var nextIds = nextEnvironments . Select ( e = > e . ConfigurationId ) . ToHashSet ( ) ;
// Remove plugins for configs that are no longer present:
foreach ( var oldEnv in CURRENT_ENVIRONMENTS )
{
if ( ! nextIds . Contains ( oldEnv . ConfigurationId ) )
{
logger . LogInformation ( "Enterprise configuration '{ConfigId}' was removed." , oldEnv . ConfigurationId ) ;
var isPluginInUse = PluginFactory . AvailablePlugins . Any ( plugin = > plugin . Id = = oldEnv . ConfigurationId ) ;
if ( isPluginInUse )
PluginFactory . RemovePluginAsync ( oldEnv . ConfigurationId ) ;
}
}
// Process new or changed configs:
foreach ( var nextEnv in nextEnvironments )
2025-06-01 19:14:21 +00:00
{
2026-02-15 17:11:57 +00:00
var currentEnv = CURRENT_ENVIRONMENTS . FirstOrDefault ( e = > e . ConfigurationId = = nextEnv . ConfigurationId ) ;
if ( currentEnv = = nextEnv ) // Hint: This relies on the record equality to check if anything relevant has changed (e.g. server URL or ETag).
2025-06-01 19:14:21 +00:00
{
2026-02-15 17:11:57 +00:00
logger . LogInformation ( "Enterprise configuration '{ConfigId}' has not changed. No update required." , nextEnv . ConfigurationId ) ;
continue ;
2025-06-01 19:14:21 +00:00
}
2026-02-15 17:11:57 +00:00
var isNew = ! currentIds . Contains ( nextEnv . ConfigurationId ) ;
if ( isNew )
logger . LogInformation ( "Detected new enterprise configuration with ID '{ConfigId}' and server URL '{ServerUrl}'." , nextEnv . ConfigurationId , nextEnv . ConfigurationServerUrl ) ;
else
logger . LogInformation ( "Detected change in enterprise configuration with ID '{ConfigId}'. Server URL or ETag has changed." , nextEnv . ConfigurationId ) ;
if ( isFirstRun )
MessageBus . INSTANCE . DeferMessage ( null , Event . STARTUP_ENTERPRISE_ENVIRONMENT , nextEnv ) ;
else
await PluginFactory . TryDownloadingConfigPluginAsync ( nextEnv . ConfigurationId , nextEnv . ConfigurationServerUrl ) ;
2025-06-01 19:14:21 +00:00
}
2026-02-15 17:11:57 +00:00
CURRENT_ENVIRONMENTS = nextEnvironments ;
2025-06-01 19:14:21 +00:00
}
catch ( Exception e )
{
logger . LogError ( e , "An error occurred while updating the enterprise environment." ) ;
}
}
}