From 8b5f5c19e4c3ab4477ead3c8dd8d61e3fb9ecfd0 Mon Sep 17 00:00:00 2001 From: Macocian Alexandru Victor Date: Sat, 17 Jan 2026 09:53:25 +0100 Subject: [PATCH] Simplify update process (Closes #1299) (#1300) --- .github/workflows/cd.yaml | 6 - Daybreak.API/EntryPoint.cs | 2 + .../Configuration/Options/LauncherOptions.cs | 4 - .../Services/Updater/ApplicationUpdater.cs | 197 +----------------- Daybreak/wwwroot/js/charting.js | 18 +- Daybreak/wwwroot/js/hover-delay-popup.js | 3 +- Daybreak/wwwroot/js/wiki-renderer.js | 1 + 7 files changed, 15 insertions(+), 216 deletions(-) diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index fb0b5854..0fc0460e 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -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: diff --git a/Daybreak.API/EntryPoint.cs b/Daybreak.API/EntryPoint.cs index 9eb5d78c..f9742729 100644 --- a/Daybreak.API/EntryPoint.cs +++ b/Daybreak.API/EntryPoint.cs @@ -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 = "")] + [SuppressMessage("AOT", "IL3057:The use of 'RequiresDynamicCodeAttribute' on entry points is disallowed since the method will be called from outside the visible app.", Justification = "")] public static int ThreadInit(IntPtr _, int __) { Environment.SetEnvironmentVariable("ASPNETCORE_HOSTINGSTARTUPASSEMBLIES", null, EnvironmentVariableTarget.Process); diff --git a/Daybreak/Configuration/Options/LauncherOptions.cs b/Daybreak/Configuration/Options/LauncherOptions.cs index c464572c..c17d98d3 100644 --- a/Daybreak/Configuration/Options/LauncherOptions.cs +++ b/Daybreak/Configuration/Options/LauncherOptions.cs @@ -32,10 +32,6 @@ public sealed class LauncherOptions [OptionRange(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; diff --git a/Daybreak/Services/Updater/ApplicationUpdater.cs b/Daybreak/Services/Updater/ApplicationUpdater.cs index 96512ac7..08c209f9 100644 --- a/Daybreak/Services/Updater/ApplicationUpdater.cs +++ b/Daybreak/Services/Updater/ApplicationUpdater.cs @@ -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 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>(); - 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 DownloadUpdateInternalBlob(List metadata, Version version, IProgress 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(); - 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 DownloadUpdateInternalLegacy(Version version, IProgress progress, CancellationToken cancellationToken) { var scopedLogger = this.logger.CreateScopedLogger(flowIdentifier: version.ToString()); diff --git a/Daybreak/wwwroot/js/charting.js b/Daybreak/wwwroot/js/charting.js index 7b72ffa1..73b6a357 100644 --- a/Daybreak/wwwroot/js/charting.js +++ b/Daybreak/wwwroot/js/charting.js @@ -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'); \ No newline at end of file +console.debug('Charting script loaded'); \ No newline at end of file diff --git a/Daybreak/wwwroot/js/hover-delay-popup.js b/Daybreak/wwwroot/js/hover-delay-popup.js index af996828..0e1a0835 100644 --- a/Daybreak/wwwroot/js/hover-delay-popup.js +++ b/Daybreak/wwwroot/js/hover-delay-popup.js @@ -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; diff --git a/Daybreak/wwwroot/js/wiki-renderer.js b/Daybreak/wwwroot/js/wiki-renderer.js index 0062c1a3..7a0a09ff 100644 --- a/Daybreak/wwwroot/js/wiki-renderer.js +++ b/Daybreak/wwwroot/js/wiki-renderer.js @@ -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);