|
|
|
@ -1,4 +1,5 @@
|
|
|
|
|
using System.Text.Json; |
|
|
|
|
using System.Globalization; |
|
|
|
|
using System.Text.Json; |
|
|
|
|
using Microsoft.Extensions.Logging; |
|
|
|
|
using Microsoft.Extensions.Options; |
|
|
|
|
using StabilityMatrix.Core.Extensions; |
|
|
|
@ -18,24 +19,31 @@ public class UpdateHelper : IUpdateHelper
|
|
|
|
|
private readonly IDownloadService downloadService; |
|
|
|
|
private readonly DebugOptions debugOptions; |
|
|
|
|
private readonly System.Timers.Timer timer = new(TimeSpan.FromMinutes(5)); |
|
|
|
|
|
|
|
|
|
private string UpdateManifestUrl => debugOptions.UpdateManifestUrl ?? |
|
|
|
|
"https://cdn.lykos.ai/update-v2.json"; |
|
|
|
|
|
|
|
|
|
private string UpdateManifestUrl => |
|
|
|
|
debugOptions.UpdateManifestUrl ?? "https://cdn.lykos.ai/update-v2.json"; |
|
|
|
|
|
|
|
|
|
public const string UpdateFolderName = ".StabilityMatrixUpdate"; |
|
|
|
|
public static DirectoryPath UpdateFolder => Compat.AppCurrentDir.JoinDir(UpdateFolderName); |
|
|
|
|
|
|
|
|
|
public static FilePath ExecutablePath => UpdateFolder.JoinFile(Compat.GetExecutableName()); |
|
|
|
|
|
|
|
|
|
public UpdateHelper(ILogger<UpdateHelper> logger, IHttpClientFactory httpClientFactory, |
|
|
|
|
IDownloadService downloadService, IOptions<DebugOptions> debugOptions) |
|
|
|
|
|
|
|
|
|
public UpdateHelper( |
|
|
|
|
ILogger<UpdateHelper> logger, |
|
|
|
|
IHttpClientFactory httpClientFactory, |
|
|
|
|
IDownloadService downloadService, |
|
|
|
|
IOptions<DebugOptions> debugOptions |
|
|
|
|
) |
|
|
|
|
{ |
|
|
|
|
this.logger = logger; |
|
|
|
|
this.httpClientFactory = httpClientFactory; |
|
|
|
|
this.downloadService = downloadService; |
|
|
|
|
this.debugOptions = debugOptions.Value; |
|
|
|
|
|
|
|
|
|
timer.Elapsed += async (_, _) => { await CheckForUpdate().ConfigureAwait(false); }; |
|
|
|
|
|
|
|
|
|
timer.Elapsed += async (_, _) => |
|
|
|
|
{ |
|
|
|
|
await CheckForUpdate().ConfigureAwait(false); |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public async Task StartCheckingForUpdates() |
|
|
|
@ -45,18 +53,33 @@ public class UpdateHelper : IUpdateHelper
|
|
|
|
|
await CheckForUpdate().ConfigureAwait(false); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public async Task DownloadUpdate(UpdateInfo updateInfo, |
|
|
|
|
IProgress<ProgressReport> progress) |
|
|
|
|
public async Task DownloadUpdate(UpdateInfo updateInfo, IProgress<ProgressReport> progress) |
|
|
|
|
{ |
|
|
|
|
var downloadUrl = updateInfo.DownloadUrl; |
|
|
|
|
|
|
|
|
|
Directory.CreateDirectory(UpdateFolder); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// download the file from URL |
|
|
|
|
await downloadService.DownloadToFileAsync(downloadUrl, ExecutablePath, progress: progress, |
|
|
|
|
httpClientName: "UpdateClient").ConfigureAwait(false); |
|
|
|
|
await downloadService |
|
|
|
|
.DownloadToFileAsync( |
|
|
|
|
downloadUrl, |
|
|
|
|
ExecutablePath, |
|
|
|
|
progress: progress, |
|
|
|
|
httpClientName: "UpdateClient" |
|
|
|
|
) |
|
|
|
|
.ConfigureAwait(false); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Format a DatetimeOffset to a culture invariant string for use in signature verification. |
|
|
|
|
/// </summary> |
|
|
|
|
private static string FormatDateTimeOffsetInvariant(DateTimeOffset dateTimeOffset) |
|
|
|
|
{ |
|
|
|
|
return dateTimeOffset.ToString( |
|
|
|
|
@"yyyy-MM-ddTHH\:mm\:ss.ffffffzzz", |
|
|
|
|
CultureInfo.InvariantCulture |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Data for use in signature verification. |
|
|
|
@ -66,10 +89,10 @@ public class UpdateHelper : IUpdateHelper
|
|
|
|
|
private static string GetUpdateInfoSignedData(UpdateInfo updateInfo) |
|
|
|
|
{ |
|
|
|
|
var channel = updateInfo.Channel.GetStringValue().ToLowerInvariant(); |
|
|
|
|
var date = updateInfo.ReleaseDate.ToString("yyyy-MM-ddTHH:mm:ss.ffffffzzz"); |
|
|
|
|
return $"{updateInfo.Version};{date};{channel};" + |
|
|
|
|
$"{(int) updateInfo.Type};{updateInfo.DownloadUrl};{updateInfo.ChangelogUrl};" + |
|
|
|
|
$"{updateInfo.HashBlake3}"; |
|
|
|
|
var date = FormatDateTimeOffsetInvariant(updateInfo.ReleaseDate); |
|
|
|
|
return $"{updateInfo.Version};{date};{channel};" |
|
|
|
|
+ $"{(int)updateInfo.Type};{updateInfo.DownloadUrl};{updateInfo.ChangelogUrl};" |
|
|
|
|
+ $"{updateInfo.HashBlake3}"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private async Task CheckForUpdate() |
|
|
|
@ -80,22 +103,26 @@ public class UpdateHelper : IUpdateHelper
|
|
|
|
|
var response = await httpClient.GetAsync(UpdateManifestUrl).ConfigureAwait(false); |
|
|
|
|
if (!response.IsSuccessStatusCode) |
|
|
|
|
{ |
|
|
|
|
logger.LogWarning("Error while checking for update {StatusCode} - {Content}", |
|
|
|
|
response.StatusCode, await response.Content.ReadAsStringAsync().ConfigureAwait(false)); |
|
|
|
|
logger.LogWarning( |
|
|
|
|
"Error while checking for update {StatusCode} - {Content}", |
|
|
|
|
response.StatusCode, |
|
|
|
|
await response.Content.ReadAsStringAsync().ConfigureAwait(false) |
|
|
|
|
); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var updateCollection = |
|
|
|
|
await JsonSerializer.DeserializeAsync<UpdateCollection>( |
|
|
|
|
await response.Content.ReadAsStreamAsync() |
|
|
|
|
.ConfigureAwait(false)).ConfigureAwait(false); |
|
|
|
|
var updateCollection = await JsonSerializer |
|
|
|
|
.DeserializeAsync<UpdateCollection>( |
|
|
|
|
await response.Content.ReadAsStreamAsync().ConfigureAwait(false) |
|
|
|
|
) |
|
|
|
|
.ConfigureAwait(false); |
|
|
|
|
|
|
|
|
|
if (updateCollection is null) |
|
|
|
|
{ |
|
|
|
|
logger.LogError("UpdateCollection is null"); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Get the update info for our platform |
|
|
|
|
var updateInfo = updateCollection switch |
|
|
|
|
{ |
|
|
|
@ -106,15 +133,18 @@ public class UpdateHelper : IUpdateHelper
|
|
|
|
|
|
|
|
|
|
if (updateInfo is null) |
|
|
|
|
{ |
|
|
|
|
logger.LogWarning("Could not find compatible update info for the platform {Platform}", Compat.Platform); |
|
|
|
|
logger.LogWarning( |
|
|
|
|
"Could not find compatible update info for the platform {Platform}", |
|
|
|
|
Compat.Platform |
|
|
|
|
); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)) |
|
|
|
@ -129,8 +159,11 @@ public class UpdateHelper : IUpdateHelper
|
|
|
|
|
if (order > 0) |
|
|
|
|
{ |
|
|
|
|
// Newer version available |
|
|
|
|
logger.LogInformation("Update available {AppVer} -> {UpdateVer}", |
|
|
|
|
Compat.AppVersion, updateInfo.Version); |
|
|
|
|
logger.LogInformation( |
|
|
|
|
"Update available {AppVer} -> {UpdateVer}", |
|
|
|
|
Compat.AppVersion, |
|
|
|
|
updateInfo.Version |
|
|
|
|
); |
|
|
|
|
EventManager.Instance.OnUpdateAvailable(updateInfo); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
@ -142,13 +175,16 @@ public class UpdateHelper : IUpdateHelper
|
|
|
|
|
// If different, we can update |
|
|
|
|
if (updateHash != appHash) |
|
|
|
|
{ |
|
|
|
|
logger.LogInformation("Update available {AppVer} -> {UpdateVer}", |
|
|
|
|
Compat.AppVersion, updateInfo.Version); |
|
|
|
|
logger.LogInformation( |
|
|
|
|
"Update available {AppVer} -> {UpdateVer}", |
|
|
|
|
Compat.AppVersion, |
|
|
|
|
updateInfo.Version |
|
|
|
|
); |
|
|
|
|
EventManager.Instance.OnUpdateAvailable(updateInfo); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("No update available"); |
|
|
|
|
} |
|
|
|
|
catch (Exception e) |
|
|
|
|