Simplify update process (Closes #1299) (#1300)

This commit is contained in:
2026-01-17 09:53:25 +01:00
parent 6d260a927d
commit 8b5f5c19e4
7 changed files with 15 additions and 216 deletions
-6
View File
@@ -92,12 +92,6 @@ jobs:
.\Scripts\BuildRelease.ps1 -version $env:Version
shell: pwsh
- name: Publish blob files
run: |
Write-Host $env
.\Scripts\PushFilesToBlobStorage.ps1 -version $env:Version -sourcePath .\Publish\ -connectionString "${{ secrets.BLOBSTORAGE_CONNECTIONSTRING }}"
shell: pwsh
- name: Create release draft
uses: Xotl/cool-github-releases@v1.1.10
with:
+2
View File
@@ -26,6 +26,8 @@ public class EntryPoint
[UnmanagedCallersOnly(EntryPoint = "ThreadInit"), STAThread]
[RequiresUnreferencedCode("The handler uses a static method that gets referenced, so there's no unreferenced code to worry about")]
[RequiresDynamicCode("The handler uses a static method, so there's no dynamic code to worry about")]
[SuppressMessage("Trimming", "IL2123:The use of 'RequiresUnreferencedCodeAttribute' on entry points is disallowed since the method will be called from outside the visible app.", Justification = "<Pending>")]
[SuppressMessage("AOT", "IL3057:The use of 'RequiresDynamicCodeAttribute' on entry points is disallowed since the method will be called from outside the visible app.", Justification = "<Pending>")]
public static int ThreadInit(IntPtr _, int __)
{
Environment.SetEnvironmentVariable("ASPNETCORE_HOSTINGSTARTUPASSEMBLIES", null, EnvironmentVariableTarget.Process);
@@ -32,10 +32,6 @@ public sealed class LauncherOptions
[OptionRange<double>(MinValue = 30, MaxValue = 300)]
public double ModStartupTimeout { get; set; } = 30;
[JsonPropertyName(nameof(BetaUpdate))]
[OptionName(Name = "Beta Update", Description = "If true, the launcher will use the new update procedure")]
public bool BetaUpdate { get; set; } = true;
[JsonPropertyName(nameof(AutoBackupSettings))]
[OptionName(Name = "Auto Backup Settings", Description = "If true, the launcher will attempt to backup settings periodically")]
public bool AutoBackupSettings { get; set; } = false;
+1 -196
View File
@@ -1,6 +1,5 @@
using Daybreak.Configuration;
using Daybreak.Configuration.Options;
using Daybreak.Services.Updater.Models;
using Daybreak.Shared.Models;
using Daybreak.Shared.Models.Async;
using Daybreak.Shared.Models.Github;
@@ -18,8 +17,6 @@ using System.Diagnostics;
using System.Extensions;
using System.Extensions.Core;
using System.Net.Http.Json;
using System.Net.WebSockets;
using System.Text;
namespace Daybreak.Services.Updater;
@@ -38,14 +35,11 @@ internal sealed class ApplicationUpdater(
private const string UpdatedKey = "LauncherUpdating";
private const string TempFileSubPath = "tempfile.zip";
private const string VersionTag = "{VERSION}";
private const string FileTag = "{FILE}";
private const string RefTagPrefix = "/refs/tags/v";
private const string VersionListUrl = "https://api.github.com/repos/gwdevhub/Daybreak/git/refs/tags";
private const string Url = "https://github.com/gwdevhub/Daybreak/releases/latest";
private const string DownloadUrl = $"https://github.com/gwdevhub/Daybreak/releases/download/v{VersionTag}/Daybreakv{VersionTag}.zip";
private const string BlobStorageUrl = $"https://daybreak.blob.core.windows.net/v{VersionTag}/{FileTag}";
private const string GithubReleasesUrl = $"https://api.github.com/repos/gwdevhub/daybreak/releases";
private const int DownloadParallelTasks = 10;
private readonly static string TempInstallerFileName = PathUtils.GetAbsolutePathFromRoot(TempInstallerFileNameSubPath);
private readonly static string InstallerFileName = PathUtils.GetAbsolutePathFromRoot(InstallerFileNameSubPath);
@@ -116,39 +110,9 @@ internal sealed class ApplicationUpdater(
public IProgressAsyncOperation<bool> DownloadUpdate(Version version, CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger(flowIdentifier: version.ToString());
if (!this.liveOptions.CurrentValue.BetaUpdate)
{
return ProgressAsyncOperation.Create(async progress =>
{
return await Task.Factory.StartNew(() => this.DownloadUpdateInternalLegacy(version, progress, cancellationToken), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).Unwrap();
}, cancellationToken);
}
return ProgressAsyncOperation.Create(async progress =>
{
try
{
progress.Report(ProgressInitialize);
var maybeMetadataResponse = await this.httpClient.GetAsync(
BlobStorageUrl
.Replace(VersionTag, version.ToString().Replace(".", "-"))
.Replace(FileTag, "Metadata.json"));
if (maybeMetadataResponse.IsSuccessStatusCode)
{
var metaData = await maybeMetadataResponse.Content.ReadFromJsonAsync<List<Metadata>>();
if (metaData is not null)
{
return await Task.Factory.StartNew(() => this.DownloadUpdateInternalBlob(metaData, version, progress, cancellationToken), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).Unwrap();
}
}
return await Task.Factory.StartNew(() => this.DownloadUpdateInternalLegacy(version, progress, cancellationToken), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).Unwrap();
}
catch (Exception e)
{
scopedLogger.LogError(e, "Failed to download update for version {version}", version);
return false;
}
return await Task.Factory.StartNew(() => this.DownloadUpdateInternalLegacy(version, progress, cancellationToken), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).Unwrap();
}, cancellationToken);
}
@@ -215,21 +179,7 @@ internal sealed class ApplicationUpdater(
var scopedLogger = this.logger.CreateScopedLogger(flowIdentifier: version.ToString());
try
{
using var blobChangeLogResponse = await this.httpClient.GetAsync(
BlobStorageUrl
.Replace(VersionTag, version.ToString().Replace(".", "-"))
.Replace(FileTag, "changelog.txt"), cancellationToken);
if (blobChangeLogResponse.IsSuccessStatusCode)
{
scopedLogger.LogDebug("Retrieved changelog from blob for version {version}", version);
var blobChangeLog = await blobChangeLogResponse.Content.ReadAsStringAsync(cancellationToken);
return blobChangeLog;
}
scopedLogger.LogWarning("Failed to retrieve changelog from blob storage for version {version}. Status code: {statusCode}", version, blobChangeLogResponse.StatusCode);
var githubChangeLogResponse = await this.GetChangeLogFromGithub(version, cancellationToken);
if (githubChangeLogResponse is null)
{
scopedLogger.LogError("Failed to retrieve changelog from github for version {version}", version);
@@ -281,151 +231,6 @@ internal sealed class ApplicationUpdater(
}
}
private async Task<bool> DownloadUpdateInternalBlob(List<Metadata> metadata, Version version, IProgress<ProgressUpdate> progress, CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger(flowIdentifier: version.ToString());
progress.Report(ProgressInitialize);
// Exclude daybreak packed files
var daybreakArchive = $"daybreak{version}.zip";
var filesToDownload = metadata
.Where(m => m.Name != daybreakArchive)
.Where(m =>
{
var fileInfo = new FileInfo(m.RelativePath!);
if (!fileInfo.Exists)
{
return true;
}
if (fileInfo.Length != m.Size)
{
return true;
}
try
{
var info = FileVersionInfo.GetVersionInfo(fileInfo.FullName);
if (info is null && m.VersionInfo is null)
{
return false;
}
if (info is null && m.VersionInfo is not null)
{
return true;
}
if (info is not null && m.VersionInfo is null)
{
return false;
}
return m.VersionInfo!.CompareTo(info!.ProductVersion) > 0;
}
catch
{
return false;
}
})
.Where(m =>
{
// Special case for Daybreak.Installer
if (m.Name != TempInstallerFileName)
{
return true;
}
var existingInstaller = new FileInfo(InstallerFileName);
return !existingInstaller.Exists || existingInstaller.Length != m.Size;
})
.ToList();
using var packageStream = new FileStream(UpdatePkg, FileMode.Create);
var downloaded = 0d;
var downloadBuffer = new byte[8192];
var sizeToDownload = (double)filesToDownload.Sum(m => m.Size);
var sw = Stopwatch.StartNew();
var lastUpdate = DateTime.Now;
var speedMeasurements = new List<double>();
var fileQueue = filesToDownload;
var pendingDownloads = fileQueue.AsEnumerable();
while(pendingDownloads.Any())
{
var parallelDownloads = pendingDownloads.Take(DownloadParallelTasks);
pendingDownloads = pendingDownloads.Skip(DownloadParallelTasks);
// Setup the download streams for all the parallel downloads
var parallelResponses = parallelDownloads.Select(file =>
{
return Task.Run(async () =>
{
var downloadUrl = BlobStorageUrl.Replace(VersionTag, version.ToString().Replace('.', '-')).Replace(FileTag, file.RelativePath);
var response = await this.httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
if (!response.IsSuccessStatusCode)
{
scopedLogger.LogError($"Error {response.StatusCode} when downloading {file.RelativePath}");
return (file, false, response);
}
return (file, true, response);
});
});
foreach(var result in parallelResponses)
{
(var file, var fileDownloadResult, var response) = await result;
if (fileDownloadResult is false ||
file is null ||
file.Name is null ||
file.RelativePath is null)
{
scopedLogger.LogError($"{file?.RelativePath ?? string.Empty} failed to download. Cancelling update");
return false;
}
var fileNameBytes = Encoding.UTF8.GetBytes(file.Name);
var relativePathBytes = Encoding.UTF8.GetBytes(file.RelativePath);
await packageStream.WriteAsync(BitConverter.GetBytes(fileNameBytes.Length), cancellationToken);
await packageStream.WriteAsync(fileNameBytes, cancellationToken);
await packageStream.WriteAsync(BitConverter.GetBytes(relativePathBytes.Length), cancellationToken);
await packageStream.WriteAsync(relativePathBytes, cancellationToken);
await packageStream.WriteAsync(BitConverter.GetBytes(file.Size), cancellationToken);
var downloadStream = await response.Content.ReadAsStreamAsync(cancellationToken);
var maxMeasurements = 100;
var fileSize = file.Size;
while (fileSize > 0)
{
var readBytes = await downloadStream.ReadAsync(downloadBuffer, cancellationToken);
await packageStream.WriteAsync(downloadBuffer.AsMemory(0, readBytes), cancellationToken);
fileSize -= readBytes;
downloaded += readBytes;
if (DateTime.Now - lastUpdate > DownloadInfoUpdateInterval)
{
lastUpdate = DateTime.Now;
var currentSpeed = downloaded / sw.ElapsedMilliseconds;
if (speedMeasurements.Count > maxMeasurements)
{
speedMeasurements.Remove(0);
}
speedMeasurements.Add(currentSpeed);
var averageSpeed = speedMeasurements.Average();
var remainingBytes = sizeToDownload - downloaded;
// var etaMillis = averageSpeed > 0 ? remainingBytes / averageSpeed : 0;
// var eta = TimeSpan.FromMilliseconds(etaMillis);
progress.Report(ProgressDownload(downloaded / sizeToDownload));
}
}
}
}
scopedLogger.LogDebug("Prepared update package at {updatePkg}", UpdatePkg);
progress.Report(ProgressFinalize);
return true;
}
private async Task<bool> DownloadUpdateInternalLegacy(Version version, IProgress<ProgressUpdate> progress, CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger(flowIdentifier: version.ToString());
+9 -9
View File
@@ -35,7 +35,7 @@ window.getChartTheme = () => {
};
window.createChart = (canvasId, config) => {
console.log('createChart called for:', canvasId);
console.debug('createChart called for:', canvasId);
const ctx = document.getElementById(canvasId);
if (!ctx) {
console.error('Canvas not found:', canvasId);
@@ -65,7 +65,7 @@ window.createChart = (canvasId, config) => {
// For WPF/WebView, we'll use linear scale instead of time scale to avoid adapter issues
if (config.options?.scales?.x?.type === 'time') {
console.log('Converting time scale to linear scale for WebView compatibility');
console.debug('Converting time scale to linear scale for WebView compatibility');
config.options.scales.x = {
type: 'linear',
title: config.options.scales.x.title || { display: true, text: 'Time' },
@@ -99,7 +99,7 @@ window.createChart = (canvasId, config) => {
try {
window.chartInstances[canvasId] = new Chart(ctx, config);
console.log('Chart created successfully:', canvasId);
console.debug('Chart created successfully:', canvasId);
return window.chartInstances[canvasId];
} catch (error) {
console.error('Error creating chart:', error);
@@ -110,12 +110,12 @@ window.createChart = (canvasId, config) => {
window.addDataPoint = (canvasId, datasetIndex, newDataPoint) => {
const chart = window.chartInstances[canvasId];
if (!chart) {
console.warn('Chart not found:', canvasId);
console.debug('Chart not found:', canvasId);
return;
}
if (!chart.data.datasets[datasetIndex]) {
console.warn('Dataset not found:', datasetIndex);
console.debug('Dataset not found:', datasetIndex);
return;
}
@@ -152,12 +152,12 @@ window.replaceChartData = (canvasId, newData) => {
};
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM loaded, checking for Chart.js...');
document.addEventListener('DOMContentLoaded', function () {
console.debug('DOM loaded, checking for Chart.js...');
function initializeCharting() {
if (typeof Chart !== 'undefined') {
console.log('Chart.js is available');
console.debug('Chart.js is available');
window.chartjsReady = true;
// Register a simple time formatter for linear scales
@@ -186,4 +186,4 @@ document.addEventListener('DOMContentLoaded', function() {
initializeCharting();
});
console.log('Charting script loaded');
console.debug('Charting script loaded');
+2 -1
View File
@@ -59,7 +59,7 @@
function start(dotNetRef, callbackMethod) {
createProgressElement();
console.debug('Hover start requested');
if (activeHover) {
stop();
}
@@ -92,6 +92,7 @@
function stop() {
activeHover = null;
console.debug('Hover stopped');
if (animationFrame) {
cancelAnimationFrame(animationFrame);
animationFrame = null;
+1
View File
@@ -1,5 +1,6 @@
window.wikiRenderer = {
render: async function (filePath, targetId) {
console.debug('wiki-renderer: Rendering', filePath, 'into', targetId);
const element = document.getElementById(targetId);
if (!element) {
console.error('wiki-renderer: Element not found:', targetId);