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 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) private static async Task Main(string[] args)
{ {
+38 -19
View File
@@ -178,11 +178,13 @@ namespace AlwaysUpToDate
using HttpResponseMessage response = await httpClient.GetAsync(updateInfoUrl); using HttpResponseMessage response = await httpClient.GetAsync(updateInfoUrl);
_ = response.EnsureSuccessStatusCode(); _ = response.EnsureSuccessStatusCode();
using Stream stream = await response.Content.ReadAsStreamAsync(); string xmlManifest = await response.Content.ReadAsStringAsync();
UpdateManifest manifest = (UpdateManifest)manifestSerializer.Deserialize(stream); UpdateManifest manifest = (UpdateManifest)manifestSerializer.Deserialize(new StringReader(xmlManifest));
TargetOS currentOS = GetCurrentOS(); 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) if (updateItem == null)
{ {
@@ -246,6 +248,18 @@ namespace AlwaysUpToDate
throw new PlatformNotSupportedException(); 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() private async Task DownloadFile()
{ {
try try
@@ -255,6 +269,8 @@ namespace AlwaysUpToDate
UpdateStarted?.Invoke(pendingUpdateItem?.Version); UpdateStarted?.Invoke(pendingUpdateItem?.Version);
TriggerProgressChanged(UpdateStep.Downloading, null, 0);
using HttpResponseMessage response = await httpClient.GetAsync(updateUrl); using HttpResponseMessage response = await httpClient.GetAsync(updateUrl);
_ = response.EnsureSuccessStatusCode(); _ = response.EnsureSuccessStatusCode();
@@ -366,6 +382,7 @@ namespace AlwaysUpToDate
} }
_ = Process.Start(new ProcessStartInfo(executablePath) { UseShellExecute = false }); _ = Process.Start(new ProcessStartInfo(executablePath) { UseShellExecute = false });
Environment.Exit(0);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -383,29 +400,31 @@ namespace AlwaysUpToDate
byte[] buffer = new byte[8192]; byte[] buffer = new byte[8192];
bool isMoreToRead = true; 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); do
if (bytesRead == 0)
{ {
isMoreToRead = false; int bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length);
TriggerProgressChanged(UpdateStep.Downloading, totalDownloadSize, totalBytesRead); if (bytesRead == 0)
continue; {
} isMoreToRead = false;
TriggerProgressChanged(UpdateStep.Downloading, totalDownloadSize, totalBytesRead);
continue;
}
await fileStream.WriteAsync(buffer, 0, bytesRead); await fileStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
readCount += 1; readCount += 1;
if (readCount >= 10) if (readCount >= 10)
{ {
readCount = 0; readCount = 0;
TriggerProgressChanged(UpdateStep.Downloading, totalDownloadSize, totalBytesRead); TriggerProgressChanged(UpdateStep.Downloading, totalDownloadSize, totalBytesRead);
}
} }
while (isMoreToRead);
} }
while (isMoreToRead);
ExtractZipFile(); ExtractZipFile();
} }
+111 -2
View File
@@ -18,14 +18,42 @@ namespace AlwaysUpToDate
/// <summary> /// <summary>
/// Represents a single update entry in the manifest, targeting a specific OS. /// 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> /// </summary>
public class UpdateItem public class UpdateItem
{ {
/// <summary> /// <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> /// </summary>
[XmlElement("os")] [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> /// <summary>
/// Gets or sets the version string of the update in <c>X.X.X.X</c> format. /// Gets or sets the version string of the update in <c>X.X.X.X</c> format.
@@ -57,6 +85,60 @@ namespace AlwaysUpToDate
/// </summary> /// </summary>
[XmlElement("checksum")] [XmlElement("checksum")]
public Checksum Checksum { get; set; } 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> /// <summary>
@@ -117,6 +199,33 @@ namespace AlwaysUpToDate
Linux, 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> /// <summary>
/// Describes the current phase of the update process, reported via <see cref="Updater.ProgressChanged"/>. /// Describes the current phase of the update process, reported via <see cref="Updater.ProgressChanged"/>.
/// </summary> /// </summary>