From c150afad4ea8d69db89a152eb17afcc3ada98d6c Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Sat, 21 Feb 2026 02:09:54 +0100 Subject: [PATCH] Add architecture support and refine OS handling in update manifest parsing --- AlwaysUpToDate.Test/Program.cs | 2 +- AlwaysUpToDate/DownloadManager.cs | 57 ++++++++++----- AlwaysUpToDate/UpdateInfo.cs | 113 +++++++++++++++++++++++++++++- 3 files changed, 150 insertions(+), 22 deletions(-) diff --git a/AlwaysUpToDate.Test/Program.cs b/AlwaysUpToDate.Test/Program.cs index daab61d..5a3017e 100644 --- a/AlwaysUpToDate.Test/Program.cs +++ b/AlwaysUpToDate.Test/Program.cs @@ -5,7 +5,7 @@ namespace AlwaysUpToDate.Test; internal class Program { - private static readonly Updater updater = new Updater(new TimeSpan(0, 0, 1), "https://raw.githubusercontent.com/Stone-Red-Code/Test/main/test.xml", true); + private static readonly Updater updater = new Updater(new TimeSpan(0, 0, 1), "https://echohub.voidcube.cloud/api/app/version", true); private static async Task Main(string[] args) { diff --git a/AlwaysUpToDate/DownloadManager.cs b/AlwaysUpToDate/DownloadManager.cs index 839fa6d..851cb1b 100644 --- a/AlwaysUpToDate/DownloadManager.cs +++ b/AlwaysUpToDate/DownloadManager.cs @@ -178,11 +178,13 @@ namespace AlwaysUpToDate using HttpResponseMessage response = await httpClient.GetAsync(updateInfoUrl); _ = response.EnsureSuccessStatusCode(); - using Stream stream = await response.Content.ReadAsStreamAsync(); - UpdateManifest manifest = (UpdateManifest)manifestSerializer.Deserialize(stream); + string xmlManifest = await response.Content.ReadAsStringAsync(); + UpdateManifest manifest = (UpdateManifest)manifestSerializer.Deserialize(new StringReader(xmlManifest)); TargetOS currentOS = GetCurrentOS(); - UpdateItem updateItem = manifest.Items?.FirstOrDefault(i => i.OS == currentOS); + TargetArchitecture currentArch = GetCurrentArchitecture(); + UpdateItem updateItem = manifest.Items?.FirstOrDefault(i => i.OS == currentOS && i.Architecture == currentArch) + ?? manifest.Items?.FirstOrDefault(i => i.OS == currentOS && i.Architecture == TargetArchitecture.Any); if (updateItem == null) { @@ -246,6 +248,18 @@ namespace AlwaysUpToDate throw new PlatformNotSupportedException(); } + private static TargetArchitecture GetCurrentArchitecture() + { + return RuntimeInformation.ProcessArchitecture switch + { + Architecture.X86 => TargetArchitecture.X86, + Architecture.X64 => TargetArchitecture.X64, + Architecture.Arm => TargetArchitecture.Arm, + Architecture.Arm64 => TargetArchitecture.Arm64, + _ => TargetArchitecture.Any, + }; + } + private async Task DownloadFile() { try @@ -255,6 +269,8 @@ namespace AlwaysUpToDate UpdateStarted?.Invoke(pendingUpdateItem?.Version); + TriggerProgressChanged(UpdateStep.Downloading, null, 0); + using HttpResponseMessage response = await httpClient.GetAsync(updateUrl); _ = response.EnsureSuccessStatusCode(); @@ -366,6 +382,7 @@ namespace AlwaysUpToDate } _ = Process.Start(new ProcessStartInfo(executablePath) { UseShellExecute = false }); + Environment.Exit(0); } catch (Exception ex) { @@ -383,29 +400,31 @@ namespace AlwaysUpToDate byte[] buffer = new byte[8192]; bool isMoreToRead = true; - using FileStream fileStream = new FileStream(Path.Join(installPath, "Update.zip"), FileMode.Create, FileAccess.Write, FileShare.None, 8192, true); - do + using (FileStream fileStream = new FileStream(Path.Join(installPath, "Update.zip"), FileMode.Create, FileAccess.Write, FileShare.None, 8192, true)) { - int bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length); - if (bytesRead == 0) + do { - isMoreToRead = false; - TriggerProgressChanged(UpdateStep.Downloading, totalDownloadSize, totalBytesRead); - continue; - } + int bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length); + if (bytesRead == 0) + { + isMoreToRead = false; + TriggerProgressChanged(UpdateStep.Downloading, totalDownloadSize, totalBytesRead); + continue; + } - await fileStream.WriteAsync(buffer, 0, bytesRead); + await fileStream.WriteAsync(buffer, 0, bytesRead); - totalBytesRead += bytesRead; - readCount += 1; + totalBytesRead += bytesRead; + readCount += 1; - if (readCount >= 10) - { - readCount = 0; - TriggerProgressChanged(UpdateStep.Downloading, totalDownloadSize, totalBytesRead); + if (readCount >= 10) + { + readCount = 0; + TriggerProgressChanged(UpdateStep.Downloading, totalDownloadSize, totalBytesRead); + } } + while (isMoreToRead); } - while (isMoreToRead); ExtractZipFile(); } diff --git a/AlwaysUpToDate/UpdateInfo.cs b/AlwaysUpToDate/UpdateInfo.cs index eb6147c..7a59b1f 100644 --- a/AlwaysUpToDate/UpdateInfo.cs +++ b/AlwaysUpToDate/UpdateInfo.cs @@ -18,14 +18,42 @@ namespace AlwaysUpToDate /// /// Represents a single update entry in the manifest, targeting a specific OS. + /// Supports both combined OS-architecture values (e.g., osx-arm64) and + /// separate <os> / <arch> elements. /// public class UpdateItem { /// - /// Gets or sets the target operating system for this update. + /// Gets or sets the raw OS string from the manifest (e.g., "windows", "osx-arm64", "linux"). + /// Supports combined OS-architecture values for backward compatibility. /// [XmlElement("os")] - public TargetOS OS { get; set; } + public string RawOS { get; set; } + + /// + /// Gets or sets the raw architecture string from the manifest. + /// When present, overrides any architecture embedded in . + /// + [XmlElement("arch")] + public string RawArchitecture { get; set; } + + /// + /// Gets the parsed target operating system, extracted from . + /// Recognizes windows/win, macos/osx, and linux prefixes. + /// + [XmlIgnore] + public TargetOS OS => ParseTargetOS(RawOS); + + /// + /// Gets the parsed target architecture. Uses if present; + /// otherwise extracts the architecture suffix from (e.g., osx-arm64). + /// Defaults to when no architecture is specified. + /// + [XmlIgnore] + public TargetArchitecture Architecture => + !string.IsNullOrEmpty(RawArchitecture) + ? ParseArchitectureString(RawArchitecture) + : ParseEmbeddedArchitecture(RawOS); /// /// Gets or sets the version string of the update in X.X.X.X format. @@ -57,6 +85,60 @@ namespace AlwaysUpToDate /// [XmlElement("checksum")] public Checksum Checksum { get; set; } + + private static TargetOS ParseTargetOS(string raw) + { + if (string.IsNullOrEmpty(raw)) + { + return default; + } + + int dashIndex = raw.IndexOf('-'); + string osPart = dashIndex >= 0 ? raw[..dashIndex] : raw; + + switch (osPart.ToLowerInvariant()) + { + case "windows": + case "win": + return TargetOS.Windows; + case "macos": + case "osx": + return TargetOS.MacOS; + case "linux": + return TargetOS.Linux; + default: + throw new System.NotSupportedException($"Unknown operating system: '{osPart}'"); + } + } + + private static TargetArchitecture ParseEmbeddedArchitecture(string raw) + { + if (string.IsNullOrEmpty(raw)) + { + return TargetArchitecture.Any; + } + + int dashIndex = raw.IndexOf('-'); + if (dashIndex < 0) + { + return TargetArchitecture.Any; + } + + return ParseArchitectureString(raw[(dashIndex + 1)..]); + } + + private static TargetArchitecture ParseArchitectureString(string raw) + { + return (raw?.ToLowerInvariant()) switch + { + "x86" => TargetArchitecture.X86, + "x64" => TargetArchitecture.X64, + "arm" => TargetArchitecture.Arm, + "arm64" => TargetArchitecture.Arm64, + "any" => TargetArchitecture.Any, + _ => throw new System.NotSupportedException($"Unknown architecture: '{raw}'"), + }; + } } /// @@ -117,6 +199,33 @@ namespace AlwaysUpToDate Linux, } + /// + /// Specifies the target processor architecture for an update item. + /// means the update applies to all architectures. + /// + public enum TargetArchitecture + { + /// Any architecture (default). Used when the update is architecture-independent. + [XmlEnum("any")] + Any, + + /// Intel/AMD 32-bit (x86). + [XmlEnum("x86")] + X86, + + /// Intel/AMD 64-bit (x64). + [XmlEnum("x64")] + X64, + + /// ARM 32-bit. + [XmlEnum("arm")] + Arm, + + /// ARM 64-bit. + [XmlEnum("arm64")] + Arm64, + } + /// /// Describes the current phase of the update process, reported via . ///