You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
128 lines
4.5 KiB
128 lines
4.5 KiB
1 year ago
|
using System.Reflection;
|
||
1 year ago
|
using System.Text.Json;
|
||
1 year ago
|
using Microsoft.Extensions.Logging;
|
||
1 year ago
|
using Microsoft.Extensions.Options;
|
||
1 year ago
|
using StabilityMatrix.Core.Extensions;
|
||
1 year ago
|
using StabilityMatrix.Core.Helper;
|
||
1 year ago
|
using StabilityMatrix.Core.Models.Configs;
|
||
1 year ago
|
using StabilityMatrix.Core.Models.Progress;
|
||
1 year ago
|
using StabilityMatrix.Core.Models.Update;
|
||
1 year ago
|
using StabilityMatrix.Core.Services;
|
||
1 year ago
|
|
||
1 year ago
|
namespace StabilityMatrix.Core.Updater;
|
||
1 year ago
|
|
||
|
public class UpdateHelper : IUpdateHelper
|
||
|
{
|
||
|
private readonly ILogger<UpdateHelper> logger;
|
||
1 year ago
|
private readonly IHttpClientFactory httpClientFactory;
|
||
|
private readonly IDownloadService downloadService;
|
||
1 year ago
|
private readonly DebugOptions debugOptions;
|
||
1 year ago
|
private readonly System.Timers.Timer timer = new(TimeSpan.FromMinutes(5));
|
||
1 year ago
|
|
||
1 year ago
|
private string UpdateManifestUrl => debugOptions.UpdateManifestUrl ??
|
||
|
"https://cdn.lykos.ai/update.json";
|
||
|
|
||
1 year ago
|
private static readonly string UpdateFolder =
|
||
|
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Update");
|
||
1 year ago
|
|
||
1 year ago
|
public static readonly string ExecutablePath =
|
||
|
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Update", "StabilityMatrix.exe");
|
||
|
|
||
|
public UpdateHelper(ILogger<UpdateHelper> logger, IHttpClientFactory httpClientFactory,
|
||
1 year ago
|
IDownloadService downloadService, IOptions<DebugOptions> debugOptions)
|
||
1 year ago
|
{
|
||
|
this.logger = logger;
|
||
1 year ago
|
this.httpClientFactory = httpClientFactory;
|
||
|
this.downloadService = downloadService;
|
||
1 year ago
|
this.debugOptions = debugOptions.Value;
|
||
1 year ago
|
|
||
|
timer.Elapsed += async (_, _) => { await CheckForUpdate(); };
|
||
1 year ago
|
}
|
||
|
|
||
1 year ago
|
public async Task StartCheckingForUpdates()
|
||
1 year ago
|
{
|
||
1 year ago
|
timer.Enabled = true;
|
||
1 year ago
|
timer.Start();
|
||
1 year ago
|
await CheckForUpdate();
|
||
1 year ago
|
}
|
||
|
|
||
1 year ago
|
public async Task DownloadUpdate(UpdateInfo updateInfo,
|
||
|
IProgress<ProgressReport> progress)
|
||
1 year ago
|
{
|
||
1 year ago
|
var downloadUrl = updateInfo.DownloadUrl;
|
||
|
|
||
|
Directory.CreateDirectory(UpdateFolder);
|
||
|
|
||
|
// download the file from URL
|
||
|
await downloadService.DownloadToFileAsync(downloadUrl, ExecutablePath, progress: progress,
|
||
|
httpClientName: "UpdateClient");
|
||
1 year ago
|
}
|
||
|
|
||
1 year ago
|
|
||
|
/// <summary>
|
||
|
/// Data for use in signature verification.
|
||
|
/// Semicolon separated string of fields:
|
||
|
/// "version, releaseDate, channel, type, url, changelog, hashBlake3"
|
||
|
/// </summary>
|
||
|
private string GetUpdateInfoSignedData(UpdateInfo updateInfo)
|
||
|
{
|
||
|
var channel = updateInfo.Channel.GetStringValue().ToLowerInvariant();
|
||
1 year ago
|
var date = updateInfo.ReleaseDate.ToString("yyyy-MM-ddTHH:mm:ss.ffffffzzz");
|
||
|
return $"{updateInfo.Version};{date};{channel};" +
|
||
1 year ago
|
$"{(int) updateInfo.Type};{updateInfo.DownloadUrl};{updateInfo.ChangelogUrl};" +
|
||
|
$"{updateInfo.HashBlake3}";
|
||
|
}
|
||
|
|
||
1 year ago
|
private async Task CheckForUpdate()
|
||
1 year ago
|
{
|
||
1 year ago
|
try
|
||
1 year ago
|
{
|
||
1 year ago
|
var httpClient = httpClientFactory.CreateClient("UpdateClient");
|
||
1 year ago
|
var response = await httpClient.GetAsync(UpdateManifestUrl);
|
||
1 year ago
|
if (!response.IsSuccessStatusCode)
|
||
|
{
|
||
|
logger.LogError("Error while checking for update");
|
||
|
return;
|
||
|
}
|
||
1 year ago
|
|
||
1 year ago
|
var updateInfo =
|
||
|
await JsonSerializer.DeserializeAsync<UpdateInfo>(
|
||
|
await response.Content.ReadAsStreamAsync());
|
||
1 year ago
|
|
||
1 year ago
|
if (updateInfo == null)
|
||
|
{
|
||
|
logger.LogError("UpdateInfo is null");
|
||
|
return;
|
||
|
}
|
||
1 year ago
|
logger.LogInformation("UpdateInfo signature: {Signature}", updateInfo.Signature);
|
||
|
|
||
|
var updateInfoSignData = GetUpdateInfoSignedData(updateInfo);
|
||
|
logger.LogInformation("UpdateInfo signed data: {SignData}", updateInfoSignData);
|
||
|
|
||
|
// Verify signature
|
||
|
var checker = new SignatureChecker();
|
||
|
if (!checker.Verify(updateInfoSignData, updateInfo.Signature))
|
||
|
{
|
||
|
logger.LogError("UpdateInfo signature is invalid: {Info}", updateInfo);
|
||
|
return;
|
||
|
}
|
||
|
logger.LogInformation("UpdateInfo signature verified");
|
||
1 year ago
|
|
||
|
var currentVersion = Assembly.GetExecutingAssembly().GetName().Version;
|
||
1 year ago
|
|
||
1 year ago
|
if (updateInfo.Version <= currentVersion)
|
||
|
{
|
||
|
logger.LogInformation("No update available");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
logger.LogInformation("Update available");
|
||
|
EventManager.Instance.OnUpdateAvailable(updateInfo);
|
||
|
}
|
||
|
catch (Exception e)
|
||
1 year ago
|
{
|
||
1 year ago
|
logger.LogError(e, "Couldn't check for update");
|
||
1 year ago
|
}
|
||
1 year ago
|
}
|
||
|
}
|