AI-Studio/app/MindWork AI Studio/Tools/Services/UpdateService.cs
Thorsten Sommer 2f81546cfe
Some checks are pending
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Improved enterprise update policies & Flatpak support (#853)
2026-07-13 13:35:24 +02:00

176 lines
5.9 KiB
C#

using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.PluginSystem;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Tools.Services;
public sealed class UpdateService : BackgroundService, IMessageBusReceiver
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(UpdateService).Namespace, nameof(UpdateService));
private static bool IS_INITIALIZED;
private static ISnackbar? SNACKBAR;
private readonly SettingsManager settingsManager;
private readonly MessageBus messageBus;
private readonly RustService rust;
private readonly UpdatePolicy updatePolicy;
private readonly ILogger<UpdateService> logger;
public UpdateService(MessageBus messageBus, SettingsManager settingsManager, RustService rust, UpdatePolicy updatePolicy, ILogger<UpdateService> logger)
{
this.settingsManager = settingsManager;
this.messageBus = messageBus;
this.rust = rust;
this.updatePolicy = updatePolicy;
this.logger = logger;
this.messageBus.RegisterComponent(this);
this.ApplyFilters([], [ Event.USER_SEARCH_FOR_UPDATE ]);
}
#region Overrides of BackgroundService
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
//
// Wait until the app is fully initialized.
//
while (!stoppingToken.IsCancellationRequested && !IS_INITIALIZED)
await Task.Delay(TimeSpan.FromSeconds(3), stoppingToken);
DateTimeOffset? lastAutomaticCheck = null;
while (!stoppingToken.IsCancellationRequested)
{
var interval = this.GetCurrentUpdateInterval();
if (this.updatePolicy.AllowsAutomaticChecks &&
(
lastAutomaticCheck is null ||
interval != Timeout.InfiniteTimeSpan && DateTimeOffset.UtcNow - lastAutomaticCheck >= interval)
)
{
await this.CheckForUpdate();
lastAutomaticCheck = DateTimeOffset.UtcNow;
}
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
this.messageBus.Unregister(this);
await base.StopAsync(cancellationToken);
}
#endregion
#region Implementation of IMessageBusReceiver
public string ComponentName => nameof(UpdateService);
public async Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
{
switch (triggeredEvent)
{
case Event.USER_SEARCH_FOR_UPDATE:
if (this.updatePolicy.AllowsManualChecks)
await this.CheckForUpdate(notifyUserWhenNoUpdate: true);
break;
}
}
public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
{
return Task.FromResult<TResult?>(default);
}
#endregion
private async Task CheckForUpdate(bool notifyUserWhenNoUpdate = false)
{
if(!IS_INITIALIZED)
return;
var response = await this.rust.CheckForUpdate();
if (response.Error)
{
this.logger.LogWarning("Update check failed. The updater did not return a usable result.");
if (notifyUserWhenNoUpdate)
{
SNACKBAR!.Add(TB("Failed to check for updates. Please try again later."), Severity.Error, config =>
{
config.Icon = Icons.Material.Filled.Error;
config.IconSize = Size.Large;
config.IconColor = Color.Error;
});
}
return;
}
if (response.UpdateIsAvailable)
{
// ReSharper disable RedundantAssignment
var isDevEnvironment = false;
#if DEBUG
isDevEnvironment = true;
#endif
// ReSharper restore RedundantAssignment
if (!isDevEnvironment && this.settingsManager.ConfigurationData.App.UpdateInstallation is UpdateInstallation.AUTOMATIC)
{
if (!this.updatePolicy.AllowsInstallations)
return;
try
{
await this.messageBus.SendMessage<bool>(null, Event.INSTALL_UPDATE);
await this.rust.InstallUpdate();
}
catch (Exception)
{
SNACKBAR!.Add(TB("Failed to install update automatically. Please try again manually."), Severity.Error, config =>
{
config.Icon = Icons.Material.Filled.Error;
config.IconSize = Size.Large;
config.IconColor = Color.Error;
});
}
}
else
await this.messageBus.SendMessage(null, Event.UPDATE_AVAILABLE, response);
}
else
{
if (notifyUserWhenNoUpdate)
{
SNACKBAR!.Add(TB("No update found."), Severity.Normal, config =>
{
config.Icon = Icons.Material.Filled.Update;
config.IconSize = Size.Large;
config.IconColor = Color.Primary;
});
}
}
}
private TimeSpan GetCurrentUpdateInterval() => this.settingsManager.ConfigurationData.App.UpdateInterval switch
{
UpdateInterval.ONCE_STARTUP => Timeout.InfiniteTimeSpan,
UpdateInterval.HOURLY => TimeSpan.FromHours(1),
UpdateInterval.DAILY => TimeSpan.FromDays(1),
UpdateInterval.WEEKLY => TimeSpan.FromDays(7),
_ => Timeout.InfiniteTimeSpan
};
public static void SetBlazorDependencies(ISnackbar snackbar)
{
SNACKBAR = snackbar;
IS_INITIALIZED = true;
}
}