Add architecture support and refine OS handling in update manifest parsing

This commit is contained in:
Stone_Red
2026-02-21 02:09:54 +01:00
parent c6853601ac
commit c150afad4e
3 changed files with 150 additions and 22 deletions
+1 -1
View File
@@ -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)
{
+23 -4
View File
@@ -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,7 +400,8 @@ 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);
using (FileStream fileStream = new FileStream(Path.Join(installPath, "Update.zip"), FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
{
do
{
int bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length);
@@ -406,6 +424,7 @@ namespace AlwaysUpToDate
}
}
while (isMoreToRead);
}
ExtractZipFile();
}
+111 -2
View File
@@ -18,14 +18,42 @@ namespace AlwaysUpToDate
/// <summary>
/// Represents a single update entry in the manifest, targeting a specific OS.
/// Supports both combined OS-architecture values (e.g., <c>osx-arm64</c>) and
/// separate <c>&lt;os&gt;</c> / <c>&lt;arch&gt;</c> elements.
/// </summary>
public class UpdateItem
{
/// <summary>
/// Gets or sets the target operating system for this update.
/// Gets or sets the raw OS string from the manifest (e.g., <c>"windows"</c>, <c>"osx-arm64"</c>, <c>"linux"</c>).
/// Supports combined OS-architecture values for backward compatibility.
/// </summary>
[XmlElement("os")]
public TargetOS OS { get; set; }
public string RawOS { get; set; }
/// <summary>
/// Gets or sets the raw architecture string from the manifest.
/// When present, overrides any architecture embedded in <see cref="RawOS"/>.
/// </summary>
[XmlElement("arch")]
public string RawArchitecture { get; set; }
/// <summary>
/// Gets the parsed target operating system, extracted from <see cref="RawOS"/>.
/// Recognizes <c>windows</c>/<c>win</c>, <c>macos</c>/<c>osx</c>, and <c>linux</c> prefixes.
/// </summary>
[XmlIgnore]
public TargetOS OS => ParseTargetOS(RawOS);
/// <summary>
/// Gets the parsed target architecture. Uses <see cref="RawArchitecture"/> if present;
/// otherwise extracts the architecture suffix from <see cref="RawOS"/> (e.g., <c>osx-arm64</c> → <see cref="TargetArchitecture.Arm64"/>).
/// Defaults to <see cref="TargetArchitecture.Any"/> when no architecture is specified.
/// </summary>
[XmlIgnore]
public TargetArchitecture Architecture =>
!string.IsNullOrEmpty(RawArchitecture)
? ParseArchitectureString(RawArchitecture)
: ParseEmbeddedArchitecture(RawOS);
/// <summary>
/// Gets or sets the version string of the update in <c>X.X.X.X</c> format.
@@ -57,6 +85,60 @@ namespace AlwaysUpToDate
/// </summary>
[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}'"),
};
}
}
/// <summary>
@@ -117,6 +199,33 @@ namespace AlwaysUpToDate
Linux,
}
/// <summary>
/// Specifies the target processor architecture for an update item.
/// <see cref="Any"/> means the update applies to all architectures.
/// </summary>
public enum TargetArchitecture
{
/// <summary>Any architecture (default). Used when the update is architecture-independent.</summary>
[XmlEnum("any")]
Any,
/// <summary>Intel/AMD 32-bit (x86).</summary>
[XmlEnum("x86")]
X86,
/// <summary>Intel/AMD 64-bit (x64).</summary>
[XmlEnum("x64")]
X64,
/// <summary>ARM 32-bit.</summary>
[XmlEnum("arm")]
Arm,
/// <summary>ARM 64-bit.</summary>
[XmlEnum("arm64")]
Arm64,
}
/// <summary>
/// Describes the current phase of the update process, reported via <see cref="Updater.ProgressChanged"/>.
/// </summary>