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 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 diff --git a/PostCodeSerialMonitor/Services/MetaUpdateService.cs b/PostCodeSerialMonitor/Services/MetaUpdateService.cs index 0204cd9..ab2e516 100644 --- a/PostCodeSerialMonitor/Services/MetaUpdateService.cs +++ b/PostCodeSerialMonitor/Services/MetaUpdateService.cs @@ -1,13 +1,16 @@ 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 MetaUpdateService { private readonly ConfigurationService _configurationService; @@ -24,8 +27,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 +37,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 +53,35 @@ 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(); var remoteMeta = await GetRemoteMetaDefinitionAsync(); - + if (localMeta == null || remoteMeta == null) { // Update required @@ -75,7 +106,7 @@ public async Task UpdateMetaDefinitionAsync() // Ensure directory exists Directory.CreateDirectory(_localPath); - + // Save the new meta definition await File.WriteAllTextAsync(LocalMetaPath, metaContentStr); @@ -143,7 +174,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 +184,23 @@ 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/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 aa01310..3c02046 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(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(); + } + } } }