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 = [ ] ;
2026-02-16 14:25:52 +00:00
public static bool HasValidEnterpriseSnapshot { get ; private set ; }
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-16 14:25:52 +00:00
HasValidEnterpriseSnapshot = false ;
2026-02-15 17:11:57 +00:00
//
2026-02-16 14:25:52 +00:00
// Step 1: Fetch all active configurations.
2026-02-15 17:11:57 +00:00
//
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
//
2026-02-16 14:25:52 +00:00
// Step 2: Determine ETags and build the next environment list.
// IMPORTANT: if we cannot read the ETag for any active configuration,
// do not mutate the plugin state and keep everything as-is.
2026-02-15 17:11:57 +00:00
//
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 ;
}
2026-02-16 14:25:52 +00:00
var etagResponse = await PluginFactory . DetermineConfigPluginETagAsync ( config . ConfigurationId , config . ConfigurationServerUrl ) ;
if ( ! etagResponse . Success )
2026-02-15 17:11:57 +00:00
{
2026-02-16 14:25:52 +00:00
logger . LogWarning ( "Failed to read enterprise config metadata for '{ConfigId}'. Keeping current plugins unchanged." , config . ConfigurationId ) ;
return ;
2026-02-15 17:11:57 +00:00
}
2026-02-16 14:25:52 +00:00
nextEnvironments . Add ( config with { ETag = etagResponse . ETag } ) ;
2026-01-24 19:17:35 +00:00
}
2026-02-15 17:11:57 +00:00
//
2026-02-16 14:25:52 +00:00
// Step 3: Compare with current environments and process changes.
// Download first. We only clean up obsolete plugins after all required
// downloads have been completed successfully.
2026-02-15 17:11:57 +00:00
//
var currentIds = CURRENT_ENVIRONMENTS . Select ( e = > e . ConfigurationId ) . ToHashSet ( ) ;
var nextIds = nextEnvironments . Select ( e = > e . ConfigurationId ) . ToHashSet ( ) ;
2026-02-16 14:25:52 +00:00
var shouldDeferStartupDownloads = isFirstRun & & ! PluginFactory . IsInitialized ;
2026-02-15 17:11:57 +00:00
// 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 ) ;
2026-02-16 14:25:52 +00:00
if ( shouldDeferStartupDownloads )
2026-02-15 17:11:57 +00:00
MessageBus . INSTANCE . DeferMessage ( null , Event . STARTUP_ENTERPRISE_ENVIRONMENT , nextEnv ) ;
else
2026-02-16 14:25:52 +00:00
{
var wasDownloadSuccessful = await PluginFactory . TryDownloadingConfigPluginAsync ( nextEnv . ConfigurationId , nextEnv . ConfigurationServerUrl ) ;
if ( ! wasDownloadSuccessful )
{
logger . LogWarning ( "Failed to update enterprise configuration '{ConfigId}'. Keeping current plugins unchanged." , nextEnv . ConfigurationId ) ;
return ;
}
}
2025-06-01 19:14:21 +00:00
}
2026-02-15 17:11:57 +00:00
2026-02-16 14:25:52 +00:00
// Cleanup is only allowed after a successful sync cycle:
if ( PluginFactory . IsInitialized & & ! shouldDeferStartupDownloads )
PluginFactory . RemoveUnreferencedManagedConfigurationPlugins ( nextIds ) ;
if ( nextEnvironments . Count = = 0 )
logger . LogInformation ( "AI Studio runs without any enterprise configurations." ) ;
2026-02-15 17:11:57 +00:00
CURRENT_ENVIRONMENTS = nextEnvironments ;
2026-02-16 14:25:52 +00:00
HasValidEnterpriseSnapshot = true ;
2025-06-01 19:14:21 +00:00
}
catch ( Exception e )
{
logger . LogError ( e , "An error occurred while updating the enterprise environment." ) ;
}
}
}