diff --git a/Knossos.NET/Classes/KnUtils.cs b/Knossos.NET/Classes/KnUtils.cs index 09c15d64..e543645a 100644 --- a/Knossos.NET/Classes/KnUtils.cs +++ b/Knossos.NET/Classes/KnUtils.cs @@ -1391,5 +1391,28 @@ public static void CreateDesktopShortcut(string shortcutName, string destFileFul Log.Add(Log.LogSeverity.Error, "KnUtils.CreateDesktopShortcut()", ex); } } + + /// + /// Determines if the current OS meets the requirements for .NET 10 / "latest" channel updates. + /// Windows 10+ or macOS 12.0+ qualify. + /// + public static bool IsModernOS() + { + if (IsWindows) + { + // Windows build 10240 = Windows 10 RTM + return Environment.OSVersion.Version.Major >= 10; + } + + if (IsMacOS) + { + var ver = Environment.OSVersion.Version; + // macOS 12.0 + return ver.Major >= 12; + } + + // Linux and anything else: treat as modern (no restriction planned) + return true; + } } } diff --git a/Knossos.NET/Models/GitHubApi.cs b/Knossos.NET/Models/GitHubApi.cs index e9bc2470..02c11194 100644 --- a/Knossos.NET/Models/GitHubApi.cs +++ b/Knossos.NET/Models/GitHubApi.cs @@ -1,6 +1,10 @@ -using System; +using Knossos.NET.Classes; +using System; +using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Net.Http.Headers; +using System.Runtime.InteropServices; using System.Text.Json; using System.Threading.Tasks; @@ -9,11 +13,27 @@ namespace Knossos.NET.Models public static class GitHubApi { /// - /// Get the last release on Knet github repo - /// URL of this repo is set on the Knossos class + /// Gets the latest applicable release from GitHub. + /// - Modern platforms (Win10+ / macOS 12.0+): uses /releases/latest (current behaviour). + /// - Legacy platforms: scans /releases, keeps only v1.3.x tags, returns the newest one. /// - /// GitHubRelease or null if the api call failed + /// GitHubRelease or null if the API call failed. public static async Task GetLastRelease() + { + if (KnUtils.IsModernOS()) + { + return await GetLatestRelease(); + } + else + { + return await GetLatestLegacyRelease(); + } + } + + /// + /// Calls /releases/latest — for modern platforms. + /// + private static async Task GetLatestRelease() { try { @@ -21,16 +41,99 @@ public static class GitHubApi client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("product", "1")); using var response = await client.GetAsync(Knossos.GitHubUpdateRepoURL + "/releases/latest"); var json = await response.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize(json)!; + return JsonSerializer.Deserialize(json); } catch (Exception ex) { - Log.Add(Log.LogSeverity.Error, "GitHubApi.GetLastRelease()", ex); + Log.Add(Log.LogSeverity.Error, "GitHubApi.GetLatestRelease()", ex); return null; } } + + /// + /// Scans /releases pages looking for v1.3.x tags and returns the newest one. + /// Stops paginating early once tags leave the 1.3.x range (assumes releases + /// are returned newest-first by the API). + /// + private static async Task GetLatestLegacyRelease() + { + try + { + var client = KnUtils.GetHttpClient(); + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("product", "1")); + + var candidates = new List(); + int page = 1; + const int perPage = 30; // GitHub default; max is 100 + + while (true) + { + var url = $"{Knossos.GitHubUpdateRepoURL}/releases?per_page={perPage}&page={page}"; + using var response = await client.GetAsync(url); + var json = await response.Content.ReadAsStringAsync(); + var releases = JsonSerializer.Deserialize>(json); + + if (releases == null || releases.Count == 0) + break; + + foreach (var release in releases) + { + if (IsLegacyTag(release.tag_name)) + { + candidates.Add(release); + } + } + + // If the oldest release on this page is already below 1.3.7, no need to go further + var lastTag = releases.Last().tag_name; + if (lastTag != null && IsBelowLegacyMinor(lastTag)) + break; + + if (releases.Count < perPage) + break; // last page + + page++; + } + + // Return the candidate with the highest semantic version + return candidates + .Where(r => r.tag_name != null) + .MaxBy(r => new SemanticVersion(NormalizeTag(r.tag_name!))); + } + catch (Exception ex) + { + Log.Add(Log.LogSeverity.Error, "GitHubApi.GetLatestLegacyRelease()", ex); + return null; + } + } + + /// + /// Returns true if the tag is a v1.3.x release (e.g. "v1.3.7", "v1.3.10-rc1"). + /// + private static bool IsLegacyTag(string? tag) + { + if (string.IsNullOrWhiteSpace(tag)) return false; + var version = new SemanticVersion(NormalizeTag(tag)); + // Check major == 1 and minor == 3 by comparing against sentinels + return SemanticVersion.Compare(NormalizeTag(tag), "1.3.0") >= 0 && + SemanticVersion.Compare(NormalizeTag(tag), "1.4.0") < 0; + } + + /// + /// Returns true if the tag is strictly older than 1.3.7 — used as an early-exit + /// hint while paginating (GitHub returns releases newest-first). + /// + private static bool IsBelowLegacyMinor(string tag) + { + return SemanticVersion.Compare(NormalizeTag(tag), "1.3.7") < 0; + } + + /// Strips the leading "v" that GitHub tags typically have. + private static string NormalizeTag(string tag) => + tag.ToLower().Replace("v", "").Trim(); } + public class GitHubRelease { public string? url { get; set; } diff --git a/Knossos.NET/ViewModels/GlobalSettingsViewModel.cs b/Knossos.NET/ViewModels/GlobalSettingsViewModel.cs index 8f3ea01f..662fe40a 100644 --- a/Knossos.NET/ViewModels/GlobalSettingsViewModel.cs +++ b/Knossos.NET/ViewModels/GlobalSettingsViewModel.cs @@ -36,7 +36,11 @@ public partial class GlobalSettingsViewModel : ViewModelBase private const long speed10MB = 170000000; /* For display only */ - [ObservableProperty] + [ObservableProperty] + internal string knossosUpdateChannelInfo = ""; + [ObservableProperty] + internal string knossosUpdateTooltip = ""; + [ObservableProperty] internal bool isPortableMode = false; [ObservableProperty] internal bool flagDataLoaded = false; @@ -552,6 +556,13 @@ public string GlobalCmd public GlobalSettingsViewModel() { isPortableMode = Knossos.inPortableMode; + if (!KnUtils.IsModernOS()) + { + KnossosUpdateChannelInfo = "(Legacy Update Channel)"; + var current = KnUtils.IsWindows ? $"Windows {Environment.OSVersion.Version.Major}" : $"MacOS {Environment.OSVersion.Version}"; + var minimum = KnUtils.IsWindows ? "Windows 10+" : "MacOS 12+"; + KnossosUpdateTooltip = $"Your version of {current} is not compatible with version 1.4 of Knossos.NET and above. To receive new features, you should upgrade to version {minimum}. You will still receive bugfix updates when they apply to version 1.3."; + } } public void CheckDisplaySettingsWarning() diff --git a/Knossos.NET/Views/GlobalSettingsView.axaml b/Knossos.NET/Views/GlobalSettingsView.axaml index db98d926..5523dd9c 100644 --- a/Knossos.NET/Views/GlobalSettingsView.axaml +++ b/Knossos.NET/Views/GlobalSettingsView.axaml @@ -73,6 +73,7 @@ +