From 0db78d8ea427fff065d145d35d9680bad3cb4cb0 Mon Sep 17 00:00:00 2001 From: "Luiz C. M. de Aquino" Date: Sun, 15 Jun 2025 13:18:31 -0300 Subject: [PATCH 01/14] Add files via upload. --- PostCodeSerialMonitor/Models/ReleaseDefinition.cs | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 PostCodeSerialMonitor/Models/ReleaseDefinition.cs diff --git a/PostCodeSerialMonitor/Models/ReleaseDefinition.cs b/PostCodeSerialMonitor/Models/ReleaseDefinition.cs new file mode 100644 index 0000000..2298b2a --- /dev/null +++ b/PostCodeSerialMonitor/Models/ReleaseDefinition.cs @@ -0,0 +1,5 @@ +namespace PostCodeSerialMonitor.Models; +public class ReleaseDefinition +{ + public string tag_name { get; set; } = string.Empty; +} \ No newline at end of file From 287ef33ea8da6c207a7e281d6d5d5a91f26970ed Mon Sep 17 00:00:00 2001 From: "Luiz C. M. de Aquino" Date: Sun, 15 Jun 2025 13:22:05 -0300 Subject: [PATCH 02/14] Add files via upload. Check for app and firmware update. --- .../Services/MetaUpdateService.cs | 72 +++++++++++++++++-- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/PostCodeSerialMonitor/Services/MetaUpdateService.cs b/PostCodeSerialMonitor/Services/MetaUpdateService.cs index 0204cd9..70338ec 100644 --- a/PostCodeSerialMonitor/Services/MetaUpdateService.cs +++ b/PostCodeSerialMonitor/Services/MetaUpdateService.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Net.Http; +using System.Reflection; using System.Text.Json; using System.Threading.Tasks; using System.Collections.Generic; @@ -8,6 +9,7 @@ using Microsoft.Extensions.Logging; namespace PostCodeSerialMonitor.Services; + public class MetaUpdateService { private readonly ConfigurationService _configurationService; @@ -24,8 +26,8 @@ public class MetaUpdateService public AppConfiguration Config => _configurationService.Config; public MetaUpdateService( - ConfigurationService configurationService, - JsonSerializerOptions jsonOptions, + ConfigurationService configurationService, + JsonSerializerOptions jsonOptions, ILogger logger) { _configurationService = configurationService ?? throw new ArgumentNullException(nameof(configurationService)); @@ -34,6 +36,10 @@ public MetaUpdateService( ?? throw new ArgumentNullException(nameof(_configurationService.Config.MetaStoragePath)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _httpClient = new HttpClient(); + + //All GitHub's API requests must include a valid User-Agent header. + //@see https://docs.github.com/en/rest/using-the-rest-api/getting-started-with-the-rest-api?apiVersion=2022-11-28#user-agent + _httpClient.DefaultRequestHeaders.Add("User-Agent", "XboxPostcodeMonitor"); } public async Task TryLoadLocalDefinition() @@ -46,11 +52,29 @@ public async Task TryLoadLocalDefinition() return true; } + public async Task CheckForAppUpdatesAsync(string currentVersion) + { + // Get the latest release from GitHub repo. + var remoteRelease = await GetRemoteAppReleaseAsync(); + var remoteVersion = (remoteRelease == null) ? string.Empty : remoteRelease.tag_name; + + return string.Compare($"{remoteVersion}", $"{currentVersion}") > 0; + } + + public async Task CheckForFirmwareUpdatesAsync(string currentVersion) + { + // Get the latest release from GitHub repo. + var remoteRelease = await GetRemoteFirmwareReleaseAsync(); + var remoteVersion = (remoteRelease == null) ? string.Empty : remoteRelease.tag_name; + + return string.Compare($"{remoteVersion}", $"{currentVersion}") > 0; + } + public async Task CheckForMetaDefinitionUpdatesAsync() { var localMeta = await GetLocalMetaDefinitionAsync(); var remoteMeta = await GetRemoteMetaDefinitionAsync(); - + if (localMeta == null || remoteMeta == null) { // Update required @@ -75,7 +99,7 @@ public async Task UpdateMetaDefinitionAsync() // Ensure directory exists Directory.CreateDirectory(_localPath); - + // Save the new meta definition await File.WriteAllTextAsync(LocalMetaPath, metaContentStr); @@ -143,7 +167,7 @@ private async Task DownloadMetaFilesAsync() { var response = await _httpClient.GetAsync(Config.MetaJsonUrl); response.EnsureSuccessStatusCode(); - + var json = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize(json, _jsonSerializeOptions); } @@ -153,4 +177,42 @@ private async Task DownloadMetaFilesAsync() return null; } } + + private async Task GetRemoteAppReleaseAsync() + { + var gitHubApiReleasesLatest = new Uri("https://api.github.com/repos/xboxoneresearch/XboxPostcodeMonitor/releases/latest"); + + try + { + var response = await _httpClient.GetAsync(gitHubApiReleasesLatest); + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(json, _jsonSerializeOptions); + } + catch (Exception ex) + { + _logger.LogError(ex, Assets.Resources.FailedDownloadReleaseDefinition, gitHubApiReleasesLatest); + return null; + } + } + + private async Task GetRemoteFirmwareReleaseAsync() + { + var gitHubApiReleasesLatest = new Uri("https://api.github.com/repos/xboxoneresearch/PicoDurangoPOST/releases/latest"); + + try + { + var response = await _httpClient.GetAsync(gitHubApiReleasesLatest); + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(json, _jsonSerializeOptions); + } + catch (Exception ex) + { + _logger.LogError(ex, Assets.Resources.FailedDownloadReleaseDefinition, gitHubApiReleasesLatest); + return null; + } + } } \ No newline at end of file From 2ae8c4ceb8348dfd5000510c1222677de8ce1e6b Mon Sep 17 00:00:00 2001 From: "Luiz C. M. de Aquino" Date: Sun, 15 Jun 2025 13:23:47 -0300 Subject: [PATCH 03/14] Add files via upload. Check for app and firmware update. --- .../ViewModels/MainWindowViewModel.cs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/PostCodeSerialMonitor/ViewModels/MainWindowViewModel.cs b/PostCodeSerialMonitor/ViewModels/MainWindowViewModel.cs index aa01310..3f61d45 100644 --- a/PostCodeSerialMonitor/ViewModels/MainWindowViewModel.cs +++ b/PostCodeSerialMonitor/ViewModels/MainWindowViewModel.cs @@ -174,6 +174,19 @@ await MessageBoxManager ButtonEnum.Ok) .ShowAsync(); } + + if (_configurationService.Config.CheckForAppUpdates) + { + updateAvailable = await _metaUpdateService.CheckForAppUpdatesAsync($"v{AppVersion}"); + if (updateAvailable) + { + var box = MessageBoxManager + .GetMessageBoxStandard(Assets.Resources.Warning, + string.Format(Assets.Resources.NewAppReleaseAvailable, "https://github.com/xboxoneresearch/XboxPostcodeMonitor/releases"), ButtonEnum.Ok); + + await box.ShowAsync(); + } + } } [RelayCommand] @@ -267,10 +280,23 @@ private async Task ConnectAsync() { _logger.LogError(ex, Assets.Resources.ErrorConection); await MessageBoxManager - .GetMessageBoxStandard(Assets.Resources.Error, string.Format(Assets.Resources.ErrorConectionMessageBoxError,ex.Message), + .GetMessageBoxStandard(Assets.Resources.Error, string.Format(Assets.Resources.ErrorConectionMessageBoxError, ex.Message), ButtonEnum.Ok) .ShowAsync(); } + + if (IsConnected && _configurationService.Config.CheckForFwUpdates) + { + var updateAvailable = await _metaUpdateService.CheckForFirmwareUpdatesAsync(_serialService.FirmwareVersion); + if (updateAvailable) + { + var box = MessageBoxManager + .GetMessageBoxStandard(Assets.Resources.Warning, + string.Format(Assets.Resources.NewFirmwareReleaseAvailable, "https://github.com/xboxoneresearch/PicoDurangoPOST/releases"), ButtonEnum.Ok); + + await box.ShowAsync(); + } + } } } From 8051005fac724dc30ac9fcdceddce5ec82ac41c5 Mon Sep 17 00:00:00 2001 From: "Luiz C. M. de Aquino" Date: Sun, 15 Jun 2025 13:24:33 -0300 Subject: [PATCH 04/14] Add files via upload. --- .../Assets/Resources.Designer.cs | 38 ++++++++++++++++++- .../Assets/Resources.pt-BR.resx | 16 ++++++++ PostCodeSerialMonitor/Assets/Resources.resx | 16 ++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/PostCodeSerialMonitor/Assets/Resources.Designer.cs b/PostCodeSerialMonitor/Assets/Resources.Designer.cs index 755ef01..03b363c 100644 --- a/PostCodeSerialMonitor/Assets/Resources.Designer.cs +++ b/PostCodeSerialMonitor/Assets/Resources.Designer.cs @@ -176,7 +176,25 @@ public static string FailedDownloadMetaDefinition { return ResourceManager.GetString("FailedDownloadMetaDefinition", resourceCulture); } } + + /// + /// In Services/MetaUpdateService.cs + /// + public static string FailedDeserializingReleaseDefinition { + get { + return ResourceManager.GetString("FailedDeserializingReleaseDefinition", resourceCulture); + } + } + /// + /// In Services/MetaUpdateService.cs + /// + public static string FailedDownloadReleaseDefinition { + get { + return ResourceManager.GetString("FailedDownloadReleaseDefinition", resourceCulture); + } + } + /// /// In Services/SerialLineDecoder.cs /// @@ -302,7 +320,25 @@ public static string FailedUpdateMetadata { return ResourceManager.GetString("FailedUpdateMetadata", resourceCulture); } } - + + /// + /// In ViewModels/MainWindowViewModel.cs + /// + public static string NewAppReleaseAvailable { + get { + return ResourceManager.GetString("NewAppReleaseAvailable", resourceCulture); + } + } + + /// + /// In ViewModels/MainWindowViewModel.cs + /// + public static string NewFirmwareReleaseAvailable { + get { + return ResourceManager.GetString("NewFirmwareReleaseAvailable", resourceCulture); + } + } + /// /// In ViewModels/MainWindowViewModel.cs /// diff --git a/PostCodeSerialMonitor/Assets/Resources.pt-BR.resx b/PostCodeSerialMonitor/Assets/Resources.pt-BR.resx index 8195cca..9afb5d7 100644 --- a/PostCodeSerialMonitor/Assets/Resources.pt-BR.resx +++ b/PostCodeSerialMonitor/Assets/Resources.pt-BR.resx @@ -169,6 +169,14 @@ Falha ao baixar MetaDefinition de {0} In Services/MetaUpdateService.cs + + Falha ao deserializar ReleaseDefinition + In Services/MetaUpdateService.cs + + + Falha ao baixar o último lançamento de {0} + In Services/MetaUpdateService.cs + Decodificador: Ignorando linha {0} In Services/SerialLineDecoder.cs @@ -225,6 +233,14 @@ Falha ao ataulizar o metadata In ViewModels/MainWindowViewModel.cs + + Uma nova versão do aplicativo está disponível em {0}. + In ViewModels/MainWindowViewModel.cs + + + Uma nova versão do firmware está disponível em {0}. + In ViewModels/MainWindowViewModel.cs + Erro In ViewModels/MainWindowViewModel.cs diff --git a/PostCodeSerialMonitor/Assets/Resources.resx b/PostCodeSerialMonitor/Assets/Resources.resx index 0336470..ca22351 100644 --- a/PostCodeSerialMonitor/Assets/Resources.resx +++ b/PostCodeSerialMonitor/Assets/Resources.resx @@ -169,6 +169,14 @@ Failed to download MetaDefinition from {0} In Services/MetaUpdateService.cs + + Failed deserializing ReleaseDefinition + In Services/MetaUpdateService.cs + + + Failed to download latest release from {0} + In Services/MetaUpdateService.cs + Decoder: Ignoring line {0} In Services/SerialLineDecoder.cs @@ -225,6 +233,14 @@ Failed to update metadata In ViewModels/MainWindowViewModel.cs + + A new app release is available at {0}. + In ViewModels/MainWindowViewModel.cs + + + A new firmware release is available at {0}. + In ViewModels/MainWindowViewModel.cs + Error In ViewModels/MainWindowViewModel.cs From 3ebf98de6c225f0fd6bb4d267d0fae6093b2b78e Mon Sep 17 00:00:00 2001 From: "Luiz C. M. de Aquino" Date: Mon, 16 Jun 2025 17:29:58 -0300 Subject: [PATCH 05/14] Adds Utils/SemanticVersionUtils.cs --- .../Services/MetaUpdateService.cs | 42 +++++++------------ .../Utils/SemanticVersionUtils.cs | 34 +++++++++++++++ .../ViewModels/MainWindowViewModel.cs | 2 +- 3 files changed, 50 insertions(+), 28 deletions(-) create mode 100644 PostCodeSerialMonitor/Utils/SemanticVersionUtils.cs diff --git a/PostCodeSerialMonitor/Services/MetaUpdateService.cs b/PostCodeSerialMonitor/Services/MetaUpdateService.cs index 70338ec..ab2e516 100644 --- a/PostCodeSerialMonitor/Services/MetaUpdateService.cs +++ b/PostCodeSerialMonitor/Services/MetaUpdateService.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using System.Collections.Generic; using PostCodeSerialMonitor.Models; +using PostCodeSerialMonitor.Utils; using Microsoft.Extensions.Logging; namespace PostCodeSerialMonitor.Services; @@ -52,22 +53,28 @@ public async Task TryLoadLocalDefinition() return true; } - public async Task CheckForAppUpdatesAsync(string currentVersion) + public async Task CheckForAppUpdatesAsync(string localVersion) { // Get the latest release from GitHub repo. - var remoteRelease = await GetRemoteAppReleaseAsync(); + var remoteRelease = await GetRepositoryLatestReleaseAsync("xboxoneresearch", "XboxPostcodeMonitor"); var remoteVersion = (remoteRelease == null) ? string.Empty : remoteRelease.tag_name; - return string.Compare($"{remoteVersion}", $"{currentVersion}") > 0; + SemanticVersionUtils local = new SemanticVersionUtils(localVersion); + SemanticVersionUtils remote = new SemanticVersionUtils(remoteVersion); + + return remote > local; } - public async Task CheckForFirmwareUpdatesAsync(string currentVersion) + public async Task CheckForFirmwareUpdatesAsync(string localVersion) { // Get the latest release from GitHub repo. - var remoteRelease = await GetRemoteFirmwareReleaseAsync(); + var remoteRelease = await GetRepositoryLatestReleaseAsync("xboxoneresearch", "PicoDurangoPOST"); var remoteVersion = (remoteRelease == null) ? string.Empty : remoteRelease.tag_name; - return string.Compare($"{remoteVersion}", $"{currentVersion}") > 0; + SemanticVersionUtils local = new SemanticVersionUtils(localVersion); + SemanticVersionUtils remote = new SemanticVersionUtils(remoteVersion); + + return remote > local; } public async Task CheckForMetaDefinitionUpdatesAsync() @@ -178,28 +185,9 @@ private async Task DownloadMetaFilesAsync() } } - private async Task GetRemoteAppReleaseAsync() - { - var gitHubApiReleasesLatest = new Uri("https://api.github.com/repos/xboxoneresearch/XboxPostcodeMonitor/releases/latest"); - - try - { - var response = await _httpClient.GetAsync(gitHubApiReleasesLatest); - response.EnsureSuccessStatusCode(); - - var json = await response.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize(json, _jsonSerializeOptions); - } - catch (Exception ex) - { - _logger.LogError(ex, Assets.Resources.FailedDownloadReleaseDefinition, gitHubApiReleasesLatest); - return null; - } - } - - private async Task GetRemoteFirmwareReleaseAsync() + private async Task GetRepositoryLatestReleaseAsync(string owner, string repo) { - var gitHubApiReleasesLatest = new Uri("https://api.github.com/repos/xboxoneresearch/PicoDurangoPOST/releases/latest"); + var gitHubApiReleasesLatest = new Uri($"https://api.github.com/repos/{owner}/{repo}/releases/latest"); try { diff --git a/PostCodeSerialMonitor/Utils/SemanticVersionUtils.cs b/PostCodeSerialMonitor/Utils/SemanticVersionUtils.cs new file mode 100644 index 0000000..aa700a8 --- /dev/null +++ b/PostCodeSerialMonitor/Utils/SemanticVersionUtils.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using Avalonia.X11.Interop; + +namespace PostCodeSerialMonitor.Utils; + +public class SemanticVersionUtils +{ + private string _version { get; set; } = string.Empty; + + public SemanticVersionUtils(string version) + { + //Ignore the 'v' at the beginning of the version string + if (version.StartsWith("v")) + { + _version = version.Substring(1); + } + else + { + _version = version; + } + } + + /// Override greater-than operator for SemanticVersionUtils + public static bool operator >(SemanticVersionUtils left, SemanticVersionUtils right) + { + return string.Compare(left._version, right._version) > 0; + } + + /// Override less-than operator for SemanticVersionUtils + public static bool operator <(SemanticVersionUtils left, SemanticVersionUtils right) + { + return string.Compare(left._version, right._version) < 0; + } +} \ No newline at end of file diff --git a/PostCodeSerialMonitor/ViewModels/MainWindowViewModel.cs b/PostCodeSerialMonitor/ViewModels/MainWindowViewModel.cs index 3f61d45..3c02046 100644 --- a/PostCodeSerialMonitor/ViewModels/MainWindowViewModel.cs +++ b/PostCodeSerialMonitor/ViewModels/MainWindowViewModel.cs @@ -177,7 +177,7 @@ await MessageBoxManager if (_configurationService.Config.CheckForAppUpdates) { - updateAvailable = await _metaUpdateService.CheckForAppUpdatesAsync($"v{AppVersion}"); + updateAvailable = await _metaUpdateService.CheckForAppUpdatesAsync(AppVersion); if (updateAvailable) { var box = MessageBoxManager From 8032a39c96c2d1ea5a7cd23de0e446772e4d66af Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Tue, 17 Jun 2025 08:15:08 +0200 Subject: [PATCH 06/14] fix/tests: SerialDecoderTests --- PostCodeSerialMonitor.Tests/SerialDecoderTests.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/PostCodeSerialMonitor.Tests/SerialDecoderTests.cs b/PostCodeSerialMonitor.Tests/SerialDecoderTests.cs index 5fb82a8..27a0e16 100644 --- a/PostCodeSerialMonitor.Tests/SerialDecoderTests.cs +++ b/PostCodeSerialMonitor.Tests/SerialDecoderTests.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Text.Json; +using Moq; namespace PostCodeSerialMonitor.Tests; public class TestDataGenerator : IEnumerable @@ -41,14 +42,17 @@ public SerialDecoderTests() // Create configuration var config = new AppConfiguration(); - var options = Options.Create(config); + var optionsMonitor = new Mock>(); + optionsMonitor.Setup(m => m.CurrentValue).Returns(config); // Create logger factory var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); + var loggerMeta = new Mock>().Object; + var loggerDecoder = new Mock>().Object; // Create configuration service var configService = new ConfigurationService( - options, + optionsMonitor.Object, loggerFactory.CreateLogger(), Path.GetDirectoryName(_testDataPath) ?? "." ); @@ -69,17 +73,17 @@ public SerialDecoderTests() ); // Create MetaDefinitionService - var metaDefinitionService = new MetaDefinitionService(configService, metaUpdateService); + var metaDefinitionService = new MetaDefinitionService(configService, metaUpdateService, loggerMeta); metaDefinitionService.RefreshMetaDefinitionsAsync().GetAwaiter().GetResult(); - _decoder = new SerialLineDecoder(metaDefinitionService); + _decoder = new SerialLineDecoder(metaDefinitionService, loggerDecoder); } [Theory] [ClassData(typeof(TestDataGenerator))] public void TestDecoding(string input, DecodedCode expected) { - var result = _decoder.DecodeLine(input, ConsoleType.XboxOne); + var result = _decoder.DecodeLine(input, ConsoleType.XboxOnePhat); Assert.NotNull(result); Assert.Equal(expected.Flavor, result.Flavor); Assert.Equal(expected.Index, result.Index); From 18a45c8f9d5eec39ee4969c5a2befae4c8d73081 Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Tue, 17 Jun 2025 08:25:22 +0200 Subject: [PATCH 07/14] chore: Split off github update checking into GithubUpdateService --- .../Services/GithubUpdateService.cs | 82 +++++++++++++++++++ .../Services/MetaUpdateService.cs | 47 ----------- .../ViewModels/MainWindowViewModel.cs | 7 +- 3 files changed, 87 insertions(+), 49 deletions(-) create mode 100644 PostCodeSerialMonitor/Services/GithubUpdateService.cs diff --git a/PostCodeSerialMonitor/Services/GithubUpdateService.cs b/PostCodeSerialMonitor/Services/GithubUpdateService.cs new file mode 100644 index 0000000..9aac90b --- /dev/null +++ b/PostCodeSerialMonitor/Services/GithubUpdateService.cs @@ -0,0 +1,82 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Reflection; +using System.Text.Json; +using System.Threading.Tasks; +using System.Collections.Generic; +using PostCodeSerialMonitor.Models; +using PostCodeSerialMonitor.Utils; +using Microsoft.Extensions.Logging; + +namespace PostCodeSerialMonitor.Services; + +public class GithubUpdateService +{ + private readonly ConfigurationService _configurationService; + private readonly JsonSerializerOptions _jsonSerializeOptions; + private readonly string _localPath; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + public AppConfiguration Config => _configurationService.Config; + + public GithubUpdateService( + ConfigurationService configurationService, + JsonSerializerOptions jsonOptions, + ILogger logger) + { + _configurationService = configurationService ?? throw new ArgumentNullException(nameof(configurationService)); + _jsonSerializeOptions = jsonOptions ?? throw new ArgumentNullException(nameof(jsonOptions)); + _localPath = _configurationService.Config.MetaStoragePath + ?? throw new ArgumentNullException(nameof(_configurationService.Config.MetaStoragePath)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _httpClient = new HttpClient(); + + //All GitHub's API requests must include a valid User-Agent header. + //@see https://docs.github.com/en/rest/using-the-rest-api/getting-started-with-the-rest-api?apiVersion=2022-11-28#user-agent + _httpClient.DefaultRequestHeaders.Add("User-Agent", "XboxPostcodeMonitor"); + } + + public async Task CheckForAppUpdatesAsync(string localVersion) + { + // Get the latest release from GitHub repo. + var remoteRelease = await GetRepositoryLatestReleaseAsync("xboxoneresearch", "XboxPostcodeMonitor"); + var remoteVersion = (remoteRelease == null) ? string.Empty : remoteRelease.tag_name; + + SemanticVersionUtils local = new SemanticVersionUtils(localVersion); + SemanticVersionUtils remote = new SemanticVersionUtils(remoteVersion); + + return remote > local; + } + + public async Task CheckForFirmwareUpdatesAsync(string localVersion) + { + // Get the latest release from GitHub repo. + var remoteRelease = await GetRepositoryLatestReleaseAsync("xboxoneresearch", "PicoDurangoPOST"); + var remoteVersion = (remoteRelease == null) ? string.Empty : remoteRelease.tag_name; + + SemanticVersionUtils local = new SemanticVersionUtils(localVersion); + SemanticVersionUtils remote = new SemanticVersionUtils(remoteVersion); + + return remote > local; + } + + private async Task GetRepositoryLatestReleaseAsync(string owner, string repo) + { + var gitHubApiReleasesLatest = new Uri($"https://api.github.com/repos/{owner}/{repo}/releases/latest"); + + try + { + var response = await _httpClient.GetAsync(gitHubApiReleasesLatest); + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(json, _jsonSerializeOptions); + } + catch (Exception ex) + { + _logger.LogError(ex, Assets.Resources.FailedDownloadReleaseDefinition, gitHubApiReleasesLatest); + return null; + } + } +} \ No newline at end of file diff --git a/PostCodeSerialMonitor/Services/MetaUpdateService.cs b/PostCodeSerialMonitor/Services/MetaUpdateService.cs index ab2e516..c2c87bb 100644 --- a/PostCodeSerialMonitor/Services/MetaUpdateService.cs +++ b/PostCodeSerialMonitor/Services/MetaUpdateService.cs @@ -37,10 +37,6 @@ public MetaUpdateService( ?? throw new ArgumentNullException(nameof(_configurationService.Config.MetaStoragePath)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _httpClient = new HttpClient(); - - //All GitHub's API requests must include a valid User-Agent header. - //@see https://docs.github.com/en/rest/using-the-rest-api/getting-started-with-the-rest-api?apiVersion=2022-11-28#user-agent - _httpClient.DefaultRequestHeaders.Add("User-Agent", "XboxPostcodeMonitor"); } public async Task TryLoadLocalDefinition() @@ -53,30 +49,6 @@ public async Task TryLoadLocalDefinition() return true; } - public async Task CheckForAppUpdatesAsync(string localVersion) - { - // Get the latest release from GitHub repo. - var remoteRelease = await GetRepositoryLatestReleaseAsync("xboxoneresearch", "XboxPostcodeMonitor"); - var remoteVersion = (remoteRelease == null) ? string.Empty : remoteRelease.tag_name; - - SemanticVersionUtils local = new SemanticVersionUtils(localVersion); - SemanticVersionUtils remote = new SemanticVersionUtils(remoteVersion); - - return remote > local; - } - - public async Task CheckForFirmwareUpdatesAsync(string localVersion) - { - // Get the latest release from GitHub repo. - var remoteRelease = await GetRepositoryLatestReleaseAsync("xboxoneresearch", "PicoDurangoPOST"); - var remoteVersion = (remoteRelease == null) ? string.Empty : remoteRelease.tag_name; - - SemanticVersionUtils local = new SemanticVersionUtils(localVersion); - SemanticVersionUtils remote = new SemanticVersionUtils(remoteVersion); - - return remote > local; - } - public async Task CheckForMetaDefinitionUpdatesAsync() { var localMeta = await GetLocalMetaDefinitionAsync(); @@ -184,23 +156,4 @@ private async Task DownloadMetaFilesAsync() return null; } } - - private async Task GetRepositoryLatestReleaseAsync(string owner, string repo) - { - var gitHubApiReleasesLatest = new Uri($"https://api.github.com/repos/{owner}/{repo}/releases/latest"); - - try - { - var response = await _httpClient.GetAsync(gitHubApiReleasesLatest); - response.EnsureSuccessStatusCode(); - - var json = await response.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize(json, _jsonSerializeOptions); - } - catch (Exception ex) - { - _logger.LogError(ex, Assets.Resources.FailedDownloadReleaseDefinition, gitHubApiReleasesLatest); - return null; - } - } } \ No newline at end of file diff --git a/PostCodeSerialMonitor/ViewModels/MainWindowViewModel.cs b/PostCodeSerialMonitor/ViewModels/MainWindowViewModel.cs index 3c02046..0228d03 100644 --- a/PostCodeSerialMonitor/ViewModels/MainWindowViewModel.cs +++ b/PostCodeSerialMonitor/ViewModels/MainWindowViewModel.cs @@ -28,6 +28,7 @@ public partial class MainWindowViewModel : ViewModelBase private SerialLineDecoder _serialLineDecoder; private MetaUpdateService _metaUpdateService; private MetaDefinitionService _metaDefinitionService; + private GithubUpdateService _githubUpdateService; private IStorageProvider? _storageProvider; public ObservableCollection SerialPorts { get; } = new(); @@ -87,6 +88,7 @@ public MainWindowViewModel( MetaUpdateService metaUpdateService, MetaDefinitionService metaDefinitionService, SerialLineDecoder serialLineDecoder, + GithubUpdateService githubUpdateService, ILogger logger) { _serialService = serialService ?? throw new ArgumentNullException(nameof(serialService)); @@ -94,6 +96,7 @@ public MainWindowViewModel( _metaUpdateService = metaUpdateService ?? throw new ArgumentNullException(nameof(metaUpdateService)); _metaDefinitionService = metaDefinitionService ?? throw new ArgumentNullException(nameof(metaDefinitionService)); _serialLineDecoder = serialLineDecoder ?? throw new ArgumentNullException(nameof(serialLineDecoder)); + _githubUpdateService = githubUpdateService ?? throw new ArgumentNullException(nameof(githubUpdateService)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); // Get version from assembly @@ -177,7 +180,7 @@ await MessageBoxManager if (_configurationService.Config.CheckForAppUpdates) { - updateAvailable = await _metaUpdateService.CheckForAppUpdatesAsync(AppVersion); + updateAvailable = await _githubUpdateService.CheckForAppUpdatesAsync(AppVersion); if (updateAvailable) { var box = MessageBoxManager @@ -287,7 +290,7 @@ await MessageBoxManager if (IsConnected && _configurationService.Config.CheckForFwUpdates) { - var updateAvailable = await _metaUpdateService.CheckForFirmwareUpdatesAsync(_serialService.FirmwareVersion); + var updateAvailable = await _githubUpdateService.CheckForFirmwareUpdatesAsync(_serialService.FirmwareVersion); if (updateAvailable) { var box = MessageBoxManager From 728896369c55fdb0034cbf72388fbf8a89925317 Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Tue, 17 Jun 2025 15:52:09 +0200 Subject: [PATCH 08/14] fix: Add GithubUpdateService to servicecollection, simplify null-check on github api response, remove obsolete usings --- PostCodeSerialMonitor/ServiceCollectionExtensions.cs | 1 + PostCodeSerialMonitor/Services/GithubUpdateService.cs | 4 ++-- PostCodeSerialMonitor/Utils/SemanticVersionUtils.cs | 3 --- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/PostCodeSerialMonitor/ServiceCollectionExtensions.cs b/PostCodeSerialMonitor/ServiceCollectionExtensions.cs index dceb8e2..adaea03 100644 --- a/PostCodeSerialMonitor/ServiceCollectionExtensions.cs +++ b/PostCodeSerialMonitor/ServiceCollectionExtensions.cs @@ -58,6 +58,7 @@ public static void AddCommonServices(this IServiceCollection collection) collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); + collection.AddSingleton(); // Register ViewModels collection.AddTransient(); diff --git a/PostCodeSerialMonitor/Services/GithubUpdateService.cs b/PostCodeSerialMonitor/Services/GithubUpdateService.cs index 9aac90b..f749eae 100644 --- a/PostCodeSerialMonitor/Services/GithubUpdateService.cs +++ b/PostCodeSerialMonitor/Services/GithubUpdateService.cs @@ -41,7 +41,7 @@ public async Task CheckForAppUpdatesAsync(string localVersion) { // Get the latest release from GitHub repo. var remoteRelease = await GetRepositoryLatestReleaseAsync("xboxoneresearch", "XboxPostcodeMonitor"); - var remoteVersion = (remoteRelease == null) ? string.Empty : remoteRelease.tag_name; + var remoteVersion = remoteRelease?.tag_name ?? string.Empty; SemanticVersionUtils local = new SemanticVersionUtils(localVersion); SemanticVersionUtils remote = new SemanticVersionUtils(remoteVersion); @@ -53,7 +53,7 @@ public async Task CheckForFirmwareUpdatesAsync(string localVersion) { // Get the latest release from GitHub repo. var remoteRelease = await GetRepositoryLatestReleaseAsync("xboxoneresearch", "PicoDurangoPOST"); - var remoteVersion = (remoteRelease == null) ? string.Empty : remoteRelease.tag_name; + var remoteVersion = remoteRelease?.tag_name ?? string.Empty; SemanticVersionUtils local = new SemanticVersionUtils(localVersion); SemanticVersionUtils remote = new SemanticVersionUtils(remoteVersion); diff --git a/PostCodeSerialMonitor/Utils/SemanticVersionUtils.cs b/PostCodeSerialMonitor/Utils/SemanticVersionUtils.cs index aa700a8..39d1ec2 100644 --- a/PostCodeSerialMonitor/Utils/SemanticVersionUtils.cs +++ b/PostCodeSerialMonitor/Utils/SemanticVersionUtils.cs @@ -1,6 +1,3 @@ -using System.Collections.Generic; -using Avalonia.X11.Interop; - namespace PostCodeSerialMonitor.Utils; public class SemanticVersionUtils From eacea5a43a829e36b4b5adb35c251632a603946f Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Tue, 17 Jun 2025 15:52:57 +0200 Subject: [PATCH 09/14] tests: Add Tests-project to dotnet-solution, implement first round of tests for SematicVersion --- PostCodeGUI.sln | 14 ++++++++ .../SemanticVersionUtilsTests.cs | 33 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 PostCodeSerialMonitor.Tests/SemanticVersionUtilsTests.cs diff --git a/PostCodeGUI.sln b/PostCodeGUI.sln index e3aa5f5..d47f62e 100644 --- a/PostCodeGUI.sln +++ b/PostCodeGUI.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostCodeSerialMonitor", "PostCodeSerialMonitor\PostCodeSerialMonitor.csproj", "{EBB8E46D-05A6-46CF-9C53-7DF64086F492}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostCodeSerialMonitor.Tests", "PostCodeSerialMonitor.Tests\PostCodeSerialMonitor.Tests.csproj", "{D629C06E-3323-47AF-83C1-B7A21D978ED2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,18 @@ Global {EBB8E46D-05A6-46CF-9C53-7DF64086F492}.Release|x64.Build.0 = Release|Any CPU {EBB8E46D-05A6-46CF-9C53-7DF64086F492}.Release|x86.ActiveCfg = Release|Any CPU {EBB8E46D-05A6-46CF-9C53-7DF64086F492}.Release|x86.Build.0 = Release|Any CPU + {D629C06E-3323-47AF-83C1-B7A21D978ED2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D629C06E-3323-47AF-83C1-B7A21D978ED2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D629C06E-3323-47AF-83C1-B7A21D978ED2}.Debug|x64.ActiveCfg = Debug|Any CPU + {D629C06E-3323-47AF-83C1-B7A21D978ED2}.Debug|x64.Build.0 = Debug|Any CPU + {D629C06E-3323-47AF-83C1-B7A21D978ED2}.Debug|x86.ActiveCfg = Debug|Any CPU + {D629C06E-3323-47AF-83C1-B7A21D978ED2}.Debug|x86.Build.0 = Debug|Any CPU + {D629C06E-3323-47AF-83C1-B7A21D978ED2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D629C06E-3323-47AF-83C1-B7A21D978ED2}.Release|Any CPU.Build.0 = Release|Any CPU + {D629C06E-3323-47AF-83C1-B7A21D978ED2}.Release|x64.ActiveCfg = Release|Any CPU + {D629C06E-3323-47AF-83C1-B7A21D978ED2}.Release|x64.Build.0 = Release|Any CPU + {D629C06E-3323-47AF-83C1-B7A21D978ED2}.Release|x86.ActiveCfg = Release|Any CPU + {D629C06E-3323-47AF-83C1-B7A21D978ED2}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PostCodeSerialMonitor.Tests/SemanticVersionUtilsTests.cs b/PostCodeSerialMonitor.Tests/SemanticVersionUtilsTests.cs new file mode 100644 index 0000000..98e677c --- /dev/null +++ b/PostCodeSerialMonitor.Tests/SemanticVersionUtilsTests.cs @@ -0,0 +1,33 @@ +using System.Collections; +using PostCodeSerialMonitor.Utils; + +namespace PostCodeSerialMonitor.Tests; +public class SemanticVersionTestDataGenerator : IEnumerable +{ + private readonly List _data = new List + { + new object[] {"0.2.0.0", "0.2.1.0", true}, + new object[] {"0.2.0.0", "0.2.10.0", true}, + new object[] {"0.2.0.0", "0.2.0.1", true}, + new object[] {"0.2.0.0", "0.3.0", true}, + new object[] {"0.2.0.0", "0.1.33.0", false}, + new object[] {"0.3.0", "0.3.1", true}, + }; + + public IEnumerator GetEnumerator() => _data.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} + +public class SemanticVersionUtilsTests +{ + [Theory] + [ClassData(typeof(SemanticVersionTestDataGenerator))] + public void TestComparison(string localVersion, string remoteVersion, bool isNewer) + { + var local = new SemanticVersionUtils(localVersion); + var remote = new SemanticVersionUtils(remoteVersion); + + Assert.Equal(isNewer, remote > local); + } +} \ No newline at end of file From 03f6c838588d98615cb6f7d7ebc3b7a02cdefece Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Tue, 17 Jun 2025 15:55:36 +0200 Subject: [PATCH 10/14] refactor: Rename SemanticVersionUtils -> SemanticVersion and move to .Models namespace --- .../SemanticVersionUtilsTests.cs | 6 +++--- .../SemanticVersion.cs} | 10 +++++----- .../Services/GithubUpdateService.cs | 12 ++++-------- 3 files changed, 12 insertions(+), 16 deletions(-) rename PostCodeSerialMonitor/{Utils/SemanticVersionUtils.cs => Models/SemanticVersion.cs} (64%) diff --git a/PostCodeSerialMonitor.Tests/SemanticVersionUtilsTests.cs b/PostCodeSerialMonitor.Tests/SemanticVersionUtilsTests.cs index 98e677c..89f07a0 100644 --- a/PostCodeSerialMonitor.Tests/SemanticVersionUtilsTests.cs +++ b/PostCodeSerialMonitor.Tests/SemanticVersionUtilsTests.cs @@ -1,5 +1,5 @@ using System.Collections; -using PostCodeSerialMonitor.Utils; +using PostCodeSerialMonitor.Models; namespace PostCodeSerialMonitor.Tests; public class SemanticVersionTestDataGenerator : IEnumerable @@ -25,8 +25,8 @@ public class SemanticVersionUtilsTests [ClassData(typeof(SemanticVersionTestDataGenerator))] public void TestComparison(string localVersion, string remoteVersion, bool isNewer) { - var local = new SemanticVersionUtils(localVersion); - var remote = new SemanticVersionUtils(remoteVersion); + var local = new SemanticVersion(localVersion); + var remote = new SemanticVersion(remoteVersion); Assert.Equal(isNewer, remote > local); } diff --git a/PostCodeSerialMonitor/Utils/SemanticVersionUtils.cs b/PostCodeSerialMonitor/Models/SemanticVersion.cs similarity index 64% rename from PostCodeSerialMonitor/Utils/SemanticVersionUtils.cs rename to PostCodeSerialMonitor/Models/SemanticVersion.cs index 39d1ec2..806d395 100644 --- a/PostCodeSerialMonitor/Utils/SemanticVersionUtils.cs +++ b/PostCodeSerialMonitor/Models/SemanticVersion.cs @@ -1,10 +1,10 @@ -namespace PostCodeSerialMonitor.Utils; +namespace PostCodeSerialMonitor.Models; -public class SemanticVersionUtils +public class SemanticVersion { private string _version { get; set; } = string.Empty; - public SemanticVersionUtils(string version) + public SemanticVersion(string version) { //Ignore the 'v' at the beginning of the version string if (version.StartsWith("v")) @@ -18,13 +18,13 @@ public SemanticVersionUtils(string version) } /// Override greater-than operator for SemanticVersionUtils - public static bool operator >(SemanticVersionUtils left, SemanticVersionUtils right) + public static bool operator >(SemanticVersion left, SemanticVersion right) { return string.Compare(left._version, right._version) > 0; } /// Override less-than operator for SemanticVersionUtils - public static bool operator <(SemanticVersionUtils left, SemanticVersionUtils right) + public static bool operator <(SemanticVersion left, SemanticVersion right) { return string.Compare(left._version, right._version) < 0; } diff --git a/PostCodeSerialMonitor/Services/GithubUpdateService.cs b/PostCodeSerialMonitor/Services/GithubUpdateService.cs index f749eae..f6f6210 100644 --- a/PostCodeSerialMonitor/Services/GithubUpdateService.cs +++ b/PostCodeSerialMonitor/Services/GithubUpdateService.cs @@ -1,12 +1,8 @@ using System; -using System.IO; using System.Net.Http; -using System.Reflection; using System.Text.Json; using System.Threading.Tasks; -using System.Collections.Generic; using PostCodeSerialMonitor.Models; -using PostCodeSerialMonitor.Utils; using Microsoft.Extensions.Logging; namespace PostCodeSerialMonitor.Services; @@ -43,8 +39,8 @@ public async Task CheckForAppUpdatesAsync(string localVersion) var remoteRelease = await GetRepositoryLatestReleaseAsync("xboxoneresearch", "XboxPostcodeMonitor"); var remoteVersion = remoteRelease?.tag_name ?? string.Empty; - SemanticVersionUtils local = new SemanticVersionUtils(localVersion); - SemanticVersionUtils remote = new SemanticVersionUtils(remoteVersion); + SemanticVersion local = new SemanticVersion(localVersion); + SemanticVersion remote = new SemanticVersion(remoteVersion); return remote > local; } @@ -55,8 +51,8 @@ public async Task CheckForFirmwareUpdatesAsync(string localVersion) var remoteRelease = await GetRepositoryLatestReleaseAsync("xboxoneresearch", "PicoDurangoPOST"); var remoteVersion = remoteRelease?.tag_name ?? string.Empty; - SemanticVersionUtils local = new SemanticVersionUtils(localVersion); - SemanticVersionUtils remote = new SemanticVersionUtils(remoteVersion); + SemanticVersion local = new SemanticVersion(localVersion); + SemanticVersion remote = new SemanticVersion(remoteVersion); return remote > local; } From 022e45c3178d37f1cfd97e114367173190a224bc Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Tue, 17 Jun 2025 16:03:43 +0200 Subject: [PATCH 11/14] tests/ci: Rename SemanticVersion test-class, run tests in CI --- .github/workflows/build.yml | 5 ++ .../SemanticVersionTests.cs | 83 +++++++++++++++++++ .../SemanticVersionUtilsTests.cs | 33 -------- 3 files changed, 88 insertions(+), 33 deletions(-) create mode 100644 PostCodeSerialMonitor.Tests/SemanticVersionTests.cs delete mode 100644 PostCodeSerialMonitor.Tests/SemanticVersionUtilsTests.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 490362d..8f53962 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -70,6 +70,11 @@ jobs: - name: Restore dependencies run: dotnet restore + - name: Test + shell: bash + run: | + dotnet test --no-restore --configuration ${{ matrix.configuration }} + - name: Build shell: bash run: | diff --git a/PostCodeSerialMonitor.Tests/SemanticVersionTests.cs b/PostCodeSerialMonitor.Tests/SemanticVersionTests.cs new file mode 100644 index 0000000..884fc37 --- /dev/null +++ b/PostCodeSerialMonitor.Tests/SemanticVersionTests.cs @@ -0,0 +1,83 @@ +using System.Collections; +using PostCodeSerialMonitor.Models; + +namespace PostCodeSerialMonitor.Tests; +public class SemanticVersionTestDataGenerator : IEnumerable +{ + private readonly List _data = new List + { + new object[] {"0.2.0.0", "0.2.1.0", true}, + new object[] {"0.2.0.0", "0.2.10.0", true}, + new object[] {"0.2.0.0", "0.2.0.1", true}, + new object[] {"0.2.0.0", "0.3.0", true}, + new object[] {"0.2.0.0", "0.1.33.0", false}, + new object[] {"0.3.0", "0.3.1", true}, + new object[] {"0.3.1.0", "0.3.11.0", true}, + new object[] {"0.3.2.0", "0.3.20.0", true}, + new object[] {"0.3.11.0", "0.3.1.0", false}, + + new object[] {"1.0.0", "1.0.1", true}, + new object[] {"1.0.0", "1.1.0", true}, + new object[] {"1.0.0", "2.0.0", true}, + new object[] {"2.0.0", "1.9.9", false}, + new object[] {"1.2.3", "1.2.4", true}, + new object[] {"1.2.3", "1.3.0", true}, + new object[] {"1.2.3", "2.0.0", true}, + new object[] {"1.2.3", "1.2.3", false}, + new object[] {"10.0.0", "9.99.99", false}, + new object[] {"0.0.1", "0.0.2", true}, + new object[] {"0.0.1", "0.1.0", true}, + new object[] {"0.0.1", "1.0.0", true}, + + new object[] {"v0.2.0.0", "v0.2.1.0", true}, + new object[] {"v0.2.0.0", "0.2.1.0", true}, + new object[] {"0.2.0.0", "v0.2.1.0", true}, + new object[] {"v1.0.0", "v1.0.1", true}, + new object[] {"v1.0.0", "1.0.1", true}, + new object[] {"1.0.0", "v1.0.1", true}, + new object[] {"v2.3.4", "v2.3.5", true}, + new object[] {"v2.3.4", "2.3.5", true}, + new object[] {"2.3.4", "v2.3.5", true}, + new object[] {"v10.20.30", "v10.20.31", true}, + new object[] {"v10.20.30", "10.20.31", true}, + new object[] {"10.20.30", "v10.20.31", true}, + + new object[] {"v0.0.1", "v0.0.2", true}, + new object[] {"v0.0.1", "0.0.2", true}, + new object[] {"0.0.1", "v0.0.2", true}, + new object[] {"v1.2.3", "v1.2.3", false}, + new object[] {"v1.2.3", "1.2.3", false}, + new object[] {"1.2.3", "v1.2.3", false}, + + new object[] {"0.1.0", "v0.2.0", true}, + new object[] {"v0.1.0", "0.2.0", true}, + new object[] {"0.1.0.0", "v0.2.0", true}, + new object[] {"v0.1.0.0", "0.2.0", true}, + new object[] {"1.0.0.0", "v1.1.0", true}, + new object[] {"v1.0.0.0", "1.1.0", true}, + + new object[] {"0.9.9.9", "v1.0.0.0", true}, + new object[] {"v0.9.9.9", "1.0.0.0", true}, + new object[] {"1.99.99.99", "v2.0.0.0", true}, + new object[] {"v1.99.99.99", "2.0.0.0", true}, + new object[] {"0.0.0.1", "v0.0.0.2", true}, + new object[] {"v0.0.0.1", "0.0.0.2", true}, + }; + + public IEnumerator GetEnumerator() => _data.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} + +public class SemanticVersionTests +{ + [Theory] + [ClassData(typeof(SemanticVersionTestDataGenerator))] + public void TestComparison(string localVersion, string remoteVersion, bool isNewer) + { + var local = new SemanticVersion(localVersion); + var remote = new SemanticVersion(remoteVersion); + + Assert.Equal(isNewer, remote > local); + } +} \ No newline at end of file diff --git a/PostCodeSerialMonitor.Tests/SemanticVersionUtilsTests.cs b/PostCodeSerialMonitor.Tests/SemanticVersionUtilsTests.cs deleted file mode 100644 index 89f07a0..0000000 --- a/PostCodeSerialMonitor.Tests/SemanticVersionUtilsTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections; -using PostCodeSerialMonitor.Models; - -namespace PostCodeSerialMonitor.Tests; -public class SemanticVersionTestDataGenerator : IEnumerable -{ - private readonly List _data = new List - { - new object[] {"0.2.0.0", "0.2.1.0", true}, - new object[] {"0.2.0.0", "0.2.10.0", true}, - new object[] {"0.2.0.0", "0.2.0.1", true}, - new object[] {"0.2.0.0", "0.3.0", true}, - new object[] {"0.2.0.0", "0.1.33.0", false}, - new object[] {"0.3.0", "0.3.1", true}, - }; - - public IEnumerator GetEnumerator() => _data.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); -} - -public class SemanticVersionUtilsTests -{ - [Theory] - [ClassData(typeof(SemanticVersionTestDataGenerator))] - public void TestComparison(string localVersion, string remoteVersion, bool isNewer) - { - var local = new SemanticVersion(localVersion); - var remote = new SemanticVersion(remoteVersion); - - Assert.Equal(isNewer, remote > local); - } -} \ No newline at end of file From 7175178fa1e72bdda58737e8fcfdc959644ec1c9 Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Tue, 17 Jun 2025 16:11:21 +0200 Subject: [PATCH 12/14] fix: SemanticVersion, make Version getter publicly accessible --- PostCodeSerialMonitor/Models/SemanticVersion.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/PostCodeSerialMonitor/Models/SemanticVersion.cs b/PostCodeSerialMonitor/Models/SemanticVersion.cs index 806d395..c71d974 100644 --- a/PostCodeSerialMonitor/Models/SemanticVersion.cs +++ b/PostCodeSerialMonitor/Models/SemanticVersion.cs @@ -2,30 +2,30 @@ namespace PostCodeSerialMonitor.Models; public class SemanticVersion { - private string _version { get; set; } = string.Empty; + public string Version { get; } = string.Empty; public SemanticVersion(string version) { //Ignore the 'v' at the beginning of the version string if (version.StartsWith("v")) { - _version = version.Substring(1); + Version = version.Substring(1); } else { - _version = version; + Version = version; } } /// Override greater-than operator for SemanticVersionUtils public static bool operator >(SemanticVersion left, SemanticVersion right) { - return string.Compare(left._version, right._version) > 0; + return string.Compare(left.Version, right.Version) > 0; } /// Override less-than operator for SemanticVersionUtils public static bool operator <(SemanticVersion left, SemanticVersion right) { - return string.Compare(left._version, right._version) < 0; + return string.Compare(left.Version, right.Version) < 0; } } \ No newline at end of file From 6d8b93686a2bccb058e3780991ff422d86f05a10 Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Fri, 20 Jun 2025 00:25:22 +0200 Subject: [PATCH 13/14] refactor: Make SemanticVersion work more reliably --- .../SemanticVersionTests.cs | 22 ++++++--- .../Models/SemanticVersion.cs | 47 +++++++++++++++++-- 2 files changed, 58 insertions(+), 11 deletions(-) diff --git a/PostCodeSerialMonitor.Tests/SemanticVersionTests.cs b/PostCodeSerialMonitor.Tests/SemanticVersionTests.cs index 884fc37..aa4faa9 100644 --- a/PostCodeSerialMonitor.Tests/SemanticVersionTests.cs +++ b/PostCodeSerialMonitor.Tests/SemanticVersionTests.cs @@ -10,12 +10,13 @@ public class SemanticVersionTestDataGenerator : IEnumerable new object[] {"0.2.0.0", "0.2.10.0", true}, new object[] {"0.2.0.0", "0.2.0.1", true}, new object[] {"0.2.0.0", "0.3.0", true}, + new object[] {"0.2.0.0", "0.1.99", false}, new object[] {"0.2.0.0", "0.1.33.0", false}, new object[] {"0.3.0", "0.3.1", true}, new object[] {"0.3.1.0", "0.3.11.0", true}, new object[] {"0.3.2.0", "0.3.20.0", true}, new object[] {"0.3.11.0", "0.3.1.0", false}, - + new object[] {"1.0.0", "1.0.1", true}, new object[] {"1.0.0", "1.1.0", true}, new object[] {"1.0.0", "2.0.0", true}, @@ -28,7 +29,7 @@ public class SemanticVersionTestDataGenerator : IEnumerable new object[] {"0.0.1", "0.0.2", true}, new object[] {"0.0.1", "0.1.0", true}, new object[] {"0.0.1", "1.0.0", true}, - + new object[] {"v0.2.0.0", "v0.2.1.0", true}, new object[] {"v0.2.0.0", "0.2.1.0", true}, new object[] {"0.2.0.0", "v0.2.1.0", true}, @@ -41,27 +42,27 @@ public class SemanticVersionTestDataGenerator : IEnumerable new object[] {"v10.20.30", "v10.20.31", true}, new object[] {"v10.20.30", "10.20.31", true}, new object[] {"10.20.30", "v10.20.31", true}, - + new object[] {"v0.0.1", "v0.0.2", true}, new object[] {"v0.0.1", "0.0.2", true}, new object[] {"0.0.1", "v0.0.2", true}, new object[] {"v1.2.3", "v1.2.3", false}, new object[] {"v1.2.3", "1.2.3", false}, new object[] {"1.2.3", "v1.2.3", false}, - + new object[] {"0.1.0", "v0.2.0", true}, new object[] {"v0.1.0", "0.2.0", true}, new object[] {"0.1.0.0", "v0.2.0", true}, new object[] {"v0.1.0.0", "0.2.0", true}, new object[] {"1.0.0.0", "v1.1.0", true}, new object[] {"v1.0.0.0", "1.1.0", true}, - + new object[] {"0.9.9.9", "v1.0.0.0", true}, new object[] {"v0.9.9.9", "1.0.0.0", true}, new object[] {"1.99.99.99", "v2.0.0.0", true}, new object[] {"v1.99.99.99", "2.0.0.0", true}, new object[] {"0.0.0.1", "v0.0.0.2", true}, - new object[] {"v0.0.0.1", "0.0.0.2", true}, + new object[] {"v0.0.0.1", "0.0.0", false}, }; public IEnumerator GetEnumerator() => _data.GetEnumerator(); @@ -71,6 +72,15 @@ public class SemanticVersionTestDataGenerator : IEnumerable public class SemanticVersionTests { + [Fact] + public void TestParsing() + { + Assert.Equal([0, 1, 2, 3], new SemanticVersion("v0.1.2.3").VersionParts); + Assert.Equal([0, 1, 2, 3], new SemanticVersion("0.1.2.3").VersionParts); + Assert.Equal([0, 1, 2, 0], new SemanticVersion("v0.1.2").VersionParts); + Assert.Equal([0, 1, 2, 0], new SemanticVersion("0.1.2").VersionParts); + } + [Theory] [ClassData(typeof(SemanticVersionTestDataGenerator))] public void TestComparison(string localVersion, string remoteVersion, bool isNewer) diff --git a/PostCodeSerialMonitor/Models/SemanticVersion.cs b/PostCodeSerialMonitor/Models/SemanticVersion.cs index c71d974..e84c352 100644 --- a/PostCodeSerialMonitor/Models/SemanticVersion.cs +++ b/PostCodeSerialMonitor/Models/SemanticVersion.cs @@ -1,11 +1,38 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + namespace PostCodeSerialMonitor.Models; -public class SemanticVersion +public partial class SemanticVersion { + public const int MIN_PARTS = 3; + public const int EXPECTED_PARTS = 4; public string Version { get; } = string.Empty; + // Format: Major.Minor.Patch. + public List VersionParts { get; } = []; + + [GeneratedRegex(@"^(?:v)?(\d+)\.(\d+)\.(\d+)\.?(\d+)?", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")] + private static partial Regex VersionPartsRegex(); + public SemanticVersion(string version) { + var match = VersionPartsRegex().Match(version); + + VersionParts = match.Groups.Values + // Skip first entry (the full matched string!) + .Skip(1) + .Select(x => x.Success ? int.Parse(x.Value) : 0) + .ToList(); + + if (VersionParts.Count < MIN_PARTS) + { + throw new InvalidDataException("Expecting at least 3 numbers in SemanticVersion"); + } + //Ignore the 'v' at the beginning of the version string if (version.StartsWith("v")) { @@ -17,15 +44,25 @@ public SemanticVersion(string version) } } - /// Override greater-than operator for SemanticVersionUtils + /// Override greater-than operator for SemanticVersion public static bool operator >(SemanticVersion left, SemanticVersion right) { - return string.Compare(left.Version, right.Version) > 0; + for (int i = 0; i < EXPECTED_PARTS; i++) + { + if (left.VersionParts[i] > right.VersionParts[i]) return true; + if (left.VersionParts[i] < right.VersionParts[i]) return false; + } + return false; } - /// Override less-than operator for SemanticVersionUtils + /// Override less-than operator for SemanticVersion public static bool operator <(SemanticVersion left, SemanticVersion right) { - return string.Compare(left.Version, right.Version) < 0; + for (int i = 0; i < EXPECTED_PARTS; i++) + { + if (left.VersionParts[i] < right.VersionParts[i]) return true; + if (left.VersionParts[i] > right.VersionParts[i]) return false; + } + return false; } } \ No newline at end of file From 8b00b3f7525e60a2c76814c40b396f2536567a25 Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Fri, 20 Jun 2025 01:57:53 +0200 Subject: [PATCH 14/14] meta: Mark test project accordingly --- PostCodeSerialMonitor.Tests/PostCodeSerialMonitor.Tests.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PostCodeSerialMonitor.Tests/PostCodeSerialMonitor.Tests.csproj b/PostCodeSerialMonitor.Tests/PostCodeSerialMonitor.Tests.csproj index f09e311..2bdb604 100644 --- a/PostCodeSerialMonitor.Tests/PostCodeSerialMonitor.Tests.csproj +++ b/PostCodeSerialMonitor.Tests/PostCodeSerialMonitor.Tests.csproj @@ -4,7 +4,9 @@ net9.0 enable enable + true false + false