Skip to content

Commit dff3e70

Browse files
fix: resolve release notes GitHub tag format (#4499)
1 parent 54009d0 commit dff3e70

File tree

4 files changed

+134
-29
lines changed

4 files changed

+134
-29
lines changed

src/UniGetUI.Avalonia/Views/Pages/ReleaseNotesWindow.axaml.cs

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Diagnostics;
2+
using System.Net;
23
using System.Net.Http.Headers;
34
using System.Text.Json;
45
using System.Text.Json.Serialization;
@@ -13,10 +14,8 @@ namespace UniGetUI.Avalonia.Views.Pages;
1314

1415
public partial class ReleaseNotesWindow : Window
1516
{
16-
private readonly string _releaseNotesUrl =
17-
$"https://github.com/Devolutions/UniGetUI/releases/tag/{CoreData.VersionName}";
18-
private readonly string _releaseNotesApiUrl =
19-
$"https://api.github.com/repos/Devolutions/UniGetUI/releases/tags/{Uri.EscapeDataString(CoreData.VersionName)}";
17+
private readonly string[] _releaseNotesTags = CoreData.GetGitHubReleaseTagCandidates();
18+
private readonly string _releaseNotesUrl = CoreData.GetGitHubReleasePageUrl();
2019

2120
private bool _hasLoadedReleaseNotes;
2221

@@ -66,6 +65,7 @@ private async void ReleaseNotesWindow_OnOpened(object? sender, EventArgs e)
6665
private async Task LoadReleaseNotesAsync()
6766
{
6867
SetLoadingState(isLoading: true);
68+
string? lastAttemptedApiUrl = null;
6969

7070
try
7171
{
@@ -75,37 +75,51 @@ private async Task LoadReleaseNotesAsync()
7575
);
7676
client.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github+json");
7777

78-
using HttpResponseMessage response = await client.GetAsync(_releaseNotesApiUrl);
79-
if (!response.IsSuccessStatusCode)
78+
foreach (string releaseTag in _releaseNotesTags)
8079
{
81-
throw new HttpRequestException(
82-
$"GitHub API returned status code {(int)response.StatusCode} ({response.StatusCode})."
80+
lastAttemptedApiUrl = CoreData.GetGitHubReleaseApiUrlFromTag(releaseTag);
81+
82+
using HttpResponseMessage response = await client.GetAsync(lastAttemptedApiUrl);
83+
if (response.StatusCode == HttpStatusCode.NotFound)
84+
{
85+
continue;
86+
}
87+
88+
if (!response.IsSuccessStatusCode)
89+
{
90+
throw new HttpRequestException(
91+
$"GitHub API returned status code {(int)response.StatusCode} ({response.StatusCode})."
92+
);
93+
}
94+
95+
await using Stream responseStream = await response.Content.ReadAsStreamAsync();
96+
GitHubReleaseResponse? release = await JsonSerializer.DeserializeAsync<GitHubReleaseResponse>(
97+
responseStream,
98+
SerializationHelpers.DefaultOptions
8399
);
84-
}
85100

86-
await using Stream responseStream = await response.Content.ReadAsStreamAsync();
87-
GitHubReleaseResponse? release = await JsonSerializer.DeserializeAsync<GitHubReleaseResponse>(
88-
responseStream,
89-
SerializationHelpers.DefaultOptions
90-
);
101+
string releaseBody = string.IsNullOrWhiteSpace(release?.Body)
102+
? CoreTools.Translate("Please try again later")
103+
: release.Body.Replace("\r\n", "\n").Trim();
91104

92-
string releaseBody = string.IsNullOrWhiteSpace(release?.Body)
93-
? CoreTools.Translate("Please try again later")
94-
: release.Body.Replace("\r\n", "\n").Trim();
105+
UrlTextBoxControl.Text = string.IsNullOrWhiteSpace(release?.HtmlUrl)
106+
? CoreData.GetGitHubReleasePageUrlFromTag(releaseTag)
107+
: release.HtmlUrl;
95108

96-
if (!string.IsNullOrWhiteSpace(release?.HtmlUrl))
97-
{
98-
UrlTextBoxControl.Text = release.HtmlUrl;
109+
ReleaseNotesTextBlockControl.Text = releaseBody;
110+
StatusBlockControl.IsVisible = false;
111+
RetryButtonControl.IsVisible = false;
112+
LoadingProgressBarControl.IsVisible = false;
113+
return;
99114
}
100115

101-
ReleaseNotesTextBlockControl.Text = releaseBody;
102-
StatusBlockControl.IsVisible = false;
103-
RetryButtonControl.IsVisible = false;
104-
LoadingProgressBarControl.IsVisible = false;
116+
throw new HttpRequestException(
117+
$"GitHub API returned 404 for all known release tags for version {CoreData.VersionName}."
118+
);
105119
}
106120
catch (Exception ex)
107121
{
108-
Logger.Error($"Failed to load release notes from {_releaseNotesApiUrl}");
122+
Logger.Error($"Failed to load release notes from {lastAttemptedApiUrl ?? _releaseNotesUrl}");
109123
Logger.Error(ex);
110124

111125
ReleaseNotesTextBlockControl.Text = string.Empty;

src/UniGetUI.Core.Data.Tests/CoreTests.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,36 @@ public void CheckOtherAttributes()
3838
"The executable file does not exist"
3939
);
4040
}
41+
42+
[Theory]
43+
[InlineData("3.3.7", "3.3.7")]
44+
[InlineData("2026.1.2", "v2026.1.2")]
45+
[InlineData("v2026.1.2", "v2026.1.2")]
46+
public void CheckGitHubReleaseTag(string versionName, string expectedTag)
47+
{
48+
Assert.Equal(expectedTag, CoreData.GetGitHubReleaseTag(versionName));
49+
}
50+
51+
[Fact]
52+
public void CheckGitHubReleaseTagCandidatesForCalendarVersion()
53+
{
54+
Assert.Equal(
55+
["v2026.1.2", "2026.1.2"],
56+
CoreData.GetGitHubReleaseTagCandidates("2026.1.2")
57+
);
58+
}
59+
60+
[Fact]
61+
public void CheckGitHubReleaseUrlsUseEscapedResolvedTag()
62+
{
63+
Assert.Equal(
64+
"https://github.com/Devolutions/UniGetUI/releases/tag/v2026.1.2",
65+
CoreData.GetGitHubReleasePageUrlFromTag(CoreData.GetGitHubReleaseTag("2026.1.2"))
66+
);
67+
Assert.Equal(
68+
"https://api.github.com/repos/Devolutions/UniGetUI/releases/tags/3.3.7-beta1",
69+
CoreData.GetGitHubReleaseApiUrlFromTag("3.3.7-beta1")
70+
);
71+
}
4172
}
4273
}

src/UniGetUI.Core.Data/CoreData.cs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ namespace UniGetUI.Core.Data
66
{
77
public static class CoreData
88
{
9+
private const string GitHubReleasePageBaseUrl = "https://github.com/Devolutions/UniGetUI/releases/tag/";
10+
private const string GitHubReleaseApiBaseUrl = "https://api.github.com/repos/Devolutions/UniGetUI/releases/tags/";
11+
912
private static int? __code_page;
1013
public static int CODE_PAGE
1114
{
1215
get => __code_page ??= GetCodePage();
1316
}
14-
public const string VersionName = "3.3.7"; // Do not modify this line, use file scripts/set-version.ps1
17+
public const string VersionName = "2026.1.0"; // Do not modify this line, use file scripts/set-version.ps1
1518
public const int BuildNumber = 106; // Do not modify this line, use file scripts/set-version.ps1
1619

1720
public const string UserAgentString =
@@ -20,6 +23,65 @@ public static int CODE_PAGE
2023
public const string AppIdentifier = "MartiCliment.UniGetUI";
2124
public const string MainWindowIdentifier = "MartiCliment.UniGetUI.MainInterface";
2225

26+
public static string GetGitHubReleaseTag()
27+
{
28+
return GetGitHubReleaseTag(VersionName);
29+
}
30+
31+
public static string[] GetGitHubReleaseTagCandidates()
32+
{
33+
return GetGitHubReleaseTagCandidates(VersionName);
34+
}
35+
36+
public static string GetGitHubReleaseTag(string versionName)
37+
{
38+
return GetGitHubReleaseTagCandidates(versionName)[0];
39+
}
40+
41+
public static string[] GetGitHubReleaseTagCandidates(string versionName)
42+
{
43+
string normalizedVersion = string.IsNullOrWhiteSpace(versionName)
44+
? VersionName
45+
: versionName.Trim();
46+
47+
if (normalizedVersion.StartsWith("v", StringComparison.OrdinalIgnoreCase))
48+
{
49+
return [normalizedVersion];
50+
}
51+
52+
string prefixedTag = $"v{normalizedVersion}";
53+
return UsesPrefixedCalendarReleaseTags(normalizedVersion)
54+
? [prefixedTag, normalizedVersion]
55+
: [normalizedVersion, prefixedTag];
56+
}
57+
58+
public static string GetGitHubReleasePageUrl()
59+
{
60+
return GetGitHubReleasePageUrlFromTag(GetGitHubReleaseTag());
61+
}
62+
63+
public static string GetGitHubReleasePageUrlFromTag(string releaseTag)
64+
{
65+
return GitHubReleasePageBaseUrl + releaseTag;
66+
}
67+
68+
public static string GetGitHubReleaseApiUrlFromTag(string releaseTag)
69+
{
70+
return GitHubReleaseApiBaseUrl + Uri.EscapeDataString(releaseTag);
71+
}
72+
73+
private static bool UsesPrefixedCalendarReleaseTags(string versionName)
74+
{
75+
int dotIndex = versionName.IndexOf('.');
76+
if (dotIndex != 4 || dotIndex == versionName.Length - 1)
77+
{
78+
return false;
79+
}
80+
81+
ReadOnlySpan<char> year = versionName.AsSpan(0, dotIndex);
82+
return int.TryParse(year, out int parsedYear) && parsedYear >= 2000;
83+
}
84+
2385
private static bool? IS_PORTABLE;
2486
private static string? PORTABLE_PATH;
2587
public static bool IsPortable

src/UniGetUI/Pages/DialogPages/ReleaseNotes.xaml.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ public ReleaseNotes()
3232
private async Task InitializeWebView()
3333
{
3434
await WebView.EnsureCoreWebView2Async();
35-
WebView.Source = new Uri(
36-
"https://github.com/Devolutions/UniGetUI/releases/tag/" + CoreData.VersionName
37-
);
35+
WebView.Source = new Uri(CoreData.GetGitHubReleasePageUrl());
3836
}
3937

4038
public void Dispose()

0 commit comments

Comments
 (0)