From 165d9feb7f22cf1dcb2722d1e24fdc9d10954eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 18 Jan 2025 18:06:53 +0100 Subject: [PATCH] feat(NewVersionCheckService): can display different messages based on required, recommened or latest version --- .../DtoVersionRecommendation.cs | 9 ++ TeslaSolarCharger/Server/Program.cs | 7 +- .../PossibleIssues/Contracts/IIssueKeys.cs | 4 +- .../Resources/PossibleIssues/IssueKeys.cs | 4 +- .../PossibleIssues/PossibleIssues.cs | 20 ++++- .../Server/Services/BackendApiService.cs | 17 ++-- .../Services/Contracts/IBackendApiService.cs | 3 +- .../Server/Services/ErrorHandlingService.cs | 3 - .../FleetTelemetryWebSocketService.cs | 1 - .../Server/Services/NewVersionCheckService.cs | 86 +++++++++++-------- .../Shared/Dtos/Contracts/ISettings.cs | 1 - .../Shared/Dtos/Settings/Settings.cs | 1 - 12 files changed, 102 insertions(+), 54 deletions(-) create mode 100644 TeslaSolarCharger/Server/Dtos/Solar4CarBackend/DtoVersionRecommendation.cs diff --git a/TeslaSolarCharger/Server/Dtos/Solar4CarBackend/DtoVersionRecommendation.cs b/TeslaSolarCharger/Server/Dtos/Solar4CarBackend/DtoVersionRecommendation.cs new file mode 100644 index 000000000..dd7f37fd5 --- /dev/null +++ b/TeslaSolarCharger/Server/Dtos/Solar4CarBackend/DtoVersionRecommendation.cs @@ -0,0 +1,9 @@ +namespace TeslaSolarCharger.Server.Dtos.Solar4CarBackend; + +public class DtoVersionRecommendation(string latestVersion, string recommendedVersion, string minimumVersion) +{ + public string LatestVersion { get; set; } = latestVersion; + public string RecommendedVersion { get; set; } = recommendedVersion; + public int? RecommendedVersionRequiredInDays { get; set; } + public string MinimumVersion { get; set; } = minimumVersion; +} diff --git a/TeslaSolarCharger/Server/Program.cs b/TeslaSolarCharger/Server/Program.cs index 28ab97d3c..35ab5f5ac 100644 --- a/TeslaSolarCharger/Server/Program.cs +++ b/TeslaSolarCharger/Server/Program.cs @@ -106,6 +106,10 @@ async Task DoStartupStuff(WebApplication webApplication, ILogger logger var teslaSolarChargerContext = webApplication.Services.GetRequiredService(); await teslaSolarChargerContext.Database.MigrateAsync().ConfigureAwait(false); + var errorHandlingService = webApplication.Services.GetRequiredService(); + await errorHandlingService.RemoveInvalidLoggedErrorsAsync().ConfigureAwait(false); + + var teslaFleetApiService = webApplication.Services.GetRequiredService(); await teslaFleetApiService.RefreshFleetApiRequestsAreAllowed(); @@ -223,11 +227,10 @@ async Task DoStartupStuff(WebApplication webApplication, ILogger logger { await jobManager.StartJobs().ConfigureAwait(false); } - var errorHandlingService = webApplication.Services.GetRequiredService(); + var issueKeys = webApplication.Services.GetRequiredService(); await errorHandlingService.HandleErrorResolved(issueKeys.CrashedOnStartup, null) .ConfigureAwait(false); - await errorHandlingService.RemoveInvalidLoggedErrorsAsync().ConfigureAwait(false); } catch (Exception ex) { diff --git a/TeslaSolarCharger/Server/Resources/PossibleIssues/Contracts/IIssueKeys.cs b/TeslaSolarCharger/Server/Resources/PossibleIssues/Contracts/IIssueKeys.cs index 7373d1c90..1fa99e7a9 100644 --- a/TeslaSolarCharger/Server/Resources/PossibleIssues/Contracts/IIssueKeys.cs +++ b/TeslaSolarCharger/Server/Resources/PossibleIssues/Contracts/IIssueKeys.cs @@ -2,7 +2,9 @@ public interface IIssueKeys { - string VersionNotUpToDate { get; } + string NewSoftwareAvailable { get; } + string NewRecommendedSoftwareAvailable { get; } + string NewRequiredSoftwareAvailable { get; } string FleetApiTokenUnauthorized { get; } string NoFleetApiToken { get; } string FleetApiTokenMissingScopes { get; } diff --git a/TeslaSolarCharger/Server/Resources/PossibleIssues/IssueKeys.cs b/TeslaSolarCharger/Server/Resources/PossibleIssues/IssueKeys.cs index fc2397de5..4f575792f 100644 --- a/TeslaSolarCharger/Server/Resources/PossibleIssues/IssueKeys.cs +++ b/TeslaSolarCharger/Server/Resources/PossibleIssues/IssueKeys.cs @@ -4,7 +4,9 @@ namespace TeslaSolarCharger.Server.Resources.PossibleIssues; public class IssueKeys : IIssueKeys { - public string VersionNotUpToDate => "VersionNotUpToDate"; + public string NewSoftwareAvailable => "NewSoftwareAvailable"; + public string NewRecommendedSoftwareAvailable => "NewRecommendedSoftwareAvailable"; + public string NewRequiredSoftwareAvailable => "NewRequiredSoftwareAvailable"; public string FleetApiTokenUnauthorized => "FleetApiTokenUnauthorized"; public string NoFleetApiToken => "NoFleetApiToken"; public string FleetApiTokenMissingScopes => "FleetApiTokenMissingScopes"; diff --git a/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs b/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs index 94544f4b4..96650c552 100644 --- a/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs +++ b/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs @@ -9,7 +9,16 @@ public class PossibleIssues(IIssueKeys issueKeys) : IPossibleIssues { private readonly Dictionary _issues = new() { - { issueKeys.VersionNotUpToDate, new DtoIssue + { issueKeys.NewSoftwareAvailable, new DtoIssue + { + IssueSeverity = IssueSeverity.Information, + IsTelegramEnabled = false, + ShowErrorAfterOccurrences = 1, + HasPlaceHolderIssueKey = false, + HideOccurrenceCount = true, + } + }, + { issueKeys.NewRecommendedSoftwareAvailable, new DtoIssue { IssueSeverity = IssueSeverity.Warning, IsTelegramEnabled = false, @@ -18,6 +27,15 @@ public class PossibleIssues(IIssueKeys issueKeys) : IPossibleIssues HideOccurrenceCount = true, } }, + { issueKeys.NewRequiredSoftwareAvailable, new DtoIssue + { + IssueSeverity = IssueSeverity.Error, + IsTelegramEnabled = false, + ShowErrorAfterOccurrences = 1, + HasPlaceHolderIssueKey = false, + HideOccurrenceCount = true, + } + }, { issueKeys.NoFleetApiToken, new DtoIssue { IssueSeverity = IssueSeverity.Error, diff --git a/TeslaSolarCharger/Server/Services/BackendApiService.cs b/TeslaSolarCharger/Server/Services/BackendApiService.cs index b1b3d9c21..1f5a7166d 100644 --- a/TeslaSolarCharger/Server/Services/BackendApiService.cs +++ b/TeslaSolarCharger/Server/Services/BackendApiService.cs @@ -7,6 +7,7 @@ using System.Reflection; using TeslaSolarCharger.Model.Contracts; using TeslaSolarCharger.Model.Entities.TeslaSolarCharger; +using TeslaSolarCharger.Server.Dtos.Solar4CarBackend; using TeslaSolarCharger.Server.Dtos.Solar4CarBackend.User; using TeslaSolarCharger.Server.Dtos.TscBackend; using TeslaSolarCharger.Server.Resources.PossibleIssues.Contracts; @@ -163,8 +164,7 @@ internal string GenerateAuthUrl(DtoTeslaOAuthRequestInformation oAuthInformation return url; } - public async Task PostInstallationInformation(string reason) - + public async Task PostInstallationInformation(string reason) { try { @@ -177,33 +177,34 @@ public async Task PostInstallationInformation(string reason) if (tokenState == TokenState.UpToDate) { var token = await teslaSolarChargerContext.BackendTokens.SingleAsync(); - var result = await SendRequestToBackend(HttpMethod.Post, token.AccessToken, + var result = await SendRequestToBackend(HttpMethod.Post, token.AccessToken, $"Client/NotifyInstallation?version={Uri.EscapeDataString(currentVersion ?? string.Empty)}&infoReason={Uri.EscapeDataString(reason)}", null); if (!result.HasError) { logger.LogInformation("Sent installation information to Backend"); - return; + return result.Data ?? throw new InvalidOperationException("Could not deserialize Version recommendation"); } logger.LogWarning("Error while sending installation information to backend. {errorMessage}", result.ErrorMessage); + throw new InvalidOperationException(result.ErrorMessage); } - var noTokenResult = await SendRequestToBackend(HttpMethod.Post, null, + var noTokenResult = await SendRequestToBackend(HttpMethod.Post, null, $"Client/NotifyInstallationAnonymous?version={Uri.EscapeDataString(currentVersion ?? string.Empty)}&infoReason={Uri.EscapeDataString(reason)}&installationId={Uri.EscapeDataString(installationId.ToString())}", null); if (!noTokenResult.HasError) { logger.LogInformation("Sent installation information to Backend"); - return; + return noTokenResult.Data ?? throw new InvalidOperationException("Could not deserialize Version recommendation"); } - logger.LogWarning("Error while sending installation information to backend. {errorMessage}", noTokenResult.ErrorMessage); + throw new InvalidOperationException(noTokenResult.ErrorMessage); } catch (Exception e) { logger.LogError(e, "Could not post installation information"); + throw; } - } public Task GetCurrentVersion() diff --git a/TeslaSolarCharger/Server/Services/Contracts/IBackendApiService.cs b/TeslaSolarCharger/Server/Services/Contracts/IBackendApiService.cs index 7b1272e14..f8fe9d064 100644 --- a/TeslaSolarCharger/Server/Services/Contracts/IBackendApiService.cs +++ b/TeslaSolarCharger/Server/Services/Contracts/IBackendApiService.cs @@ -1,4 +1,5 @@ using TeslaSolarCharger.Model.Entities.TeslaSolarCharger; +using TeslaSolarCharger.Server.Dtos.Solar4CarBackend; using TeslaSolarCharger.Shared.Dtos; namespace TeslaSolarCharger.Server.Services.Contracts; @@ -6,7 +7,7 @@ namespace TeslaSolarCharger.Server.Services.Contracts; public interface IBackendApiService { Task> StartTeslaOAuth(string locale, string baseUrl); - Task PostInstallationInformation(string reason); + Task PostInstallationInformation(string reason); Task GetCurrentVersion(); Task GetNewBackendNotifications(); Task GetToken(DtoBackendLogin login); diff --git a/TeslaSolarCharger/Server/Services/ErrorHandlingService.cs b/TeslaSolarCharger/Server/Services/ErrorHandlingService.cs index ecd68c7a9..f167c3dc2 100644 --- a/TeslaSolarCharger/Server/Services/ErrorHandlingService.cs +++ b/TeslaSolarCharger/Server/Services/ErrorHandlingService.cs @@ -135,9 +135,6 @@ await context.ModbusResultConfigurations.Where(r => r.UsedFor <= ValueUsage.Home await AddOrRemoveErrors(activeErrors, issueKeys.SolarValuesNotAvailable, "Solar values are not available", $"Solar values are {pvValueUpdateAge} old. It looks like there is something wrong when trying to get the solar values.", solarValuesTooOld).ConfigureAwait(false); - await AddOrRemoveErrors(activeErrors, issueKeys.VersionNotUpToDate, "New software version available", - "Update TSC to the latest version.", settings.IsNewVersionAvailable).ConfigureAwait(false); - //ToDO: fix next line, currently not working due to cyclic reference //await AddOrRemoveErrors(activeErrors, issueKeys.BaseAppNotLicensed, "Base App not licensed", // "Can not send commands to car as app is not licensed", !await backendApiService.IsBaseAppLicensed(true)); diff --git a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs index 4a99eb9dd..8e23d7455 100644 --- a/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs +++ b/TeslaSolarCharger/Server/Services/FleetTelemetryWebSocketService.cs @@ -4,7 +4,6 @@ using System.Globalization; using System.Net.WebSockets; using System.Text; -using TeslaSolarCharger.Model.Entities.TeslaMate; using TeslaSolarCharger.Model.Entities.TeslaSolarCharger; using TeslaSolarCharger.Model.EntityFramework; using TeslaSolarCharger.Server.Dtos; diff --git a/TeslaSolarCharger/Server/Services/NewVersionCheckService.cs b/TeslaSolarCharger/Server/Services/NewVersionCheckService.cs index 4116c5729..51fbeb73d 100644 --- a/TeslaSolarCharger/Server/Services/NewVersionCheckService.cs +++ b/TeslaSolarCharger/Server/Services/NewVersionCheckService.cs @@ -1,5 +1,6 @@ using TeslaSolarCharger.Server.Contracts; using TeslaSolarCharger.Server.Dtos.TscBackend; +using TeslaSolarCharger.Server.Resources.PossibleIssues.Contracts; using TeslaSolarCharger.Server.Services.Contracts; using TeslaSolarCharger.Shared.Contracts; using TeslaSolarCharger.Shared.Dtos.Contracts; @@ -12,64 +13,81 @@ public class NewVersionCheckService : INewVersionCheckService private readonly ICoreService _coreService; private readonly ISettings _settings; private readonly IBackendApiService _backendApiService; + private readonly IErrorHandlingService _errorHandlingService; + private readonly IIssueKeys _issueKeys; public NewVersionCheckService(ILogger logger, ICoreService coreService, ISettings settings, - IBackendApiService backendApiService) + IBackendApiService backendApiService, IErrorHandlingService errorHandlingService, IIssueKeys issueKeys) { _logger = logger; _coreService = coreService; _settings = settings; _backendApiService = backendApiService; + _errorHandlingService = errorHandlingService; + _issueKeys = issueKeys; } public async Task CheckForNewVersion() { _logger.LogTrace("{method}()", nameof(CheckForNewVersion)); var currentVersion = await _coreService.GetCurrentVersion().ConfigureAwait(false); - await _backendApiService.PostInstallationInformation("CheckForNewVersion").ConfigureAwait(false); - if (string.IsNullOrEmpty(currentVersion)) + var versionRecommendation = await _backendApiService.PostInstallationInformation("CheckForNewVersion").ConfigureAwait(false); + var couldParseLocalVersion = Version.TryParse(currentVersion, out var localVersion); + if (!couldParseLocalVersion) { - _settings.IsNewVersionAvailable = false; - return; - } - - - try - { - if (currentVersion.Contains("-")) - { - currentVersion = currentVersion.Split("-").First(); - } - var localVersion = Version.Parse(currentVersion); - _logger.LogDebug("Local version is {localVersion}", localVersion); - var finalUrl = await GetRedirectedUrlAsync("https://github.com/pkuehnel/TeslaSolarCharger/releases/latest").ConfigureAwait(false); - if (string.IsNullOrEmpty(finalUrl)) + if (string.IsNullOrEmpty(currentVersion)) { - _settings.IsNewVersionAvailable = false; + _logger.LogError("Could not get local version"); return; } - var tag = finalUrl.Split("/").Last(); - var githubVersionString = tag.Substring(1); - if (string.IsNullOrEmpty(githubVersionString)) + var splittedVersionString= currentVersion.Split("-")[0]; + couldParseLocalVersion = Version.TryParse(splittedVersionString, out localVersion); + if(!couldParseLocalVersion || localVersion == default) { - _settings.IsNewVersionAvailable = false; - return; - } - var githubVersion = Version.Parse(githubVersionString); - _logger.LogDebug("Local version is {githubVersion}", githubVersionString); - if (githubVersion > localVersion) - { - _settings.IsNewVersionAvailable = true; + _logger.LogError("Could not parse local version {currentVersion}", currentVersion); return; } + var buildToUse = localVersion.Build > 0 ? localVersion.Build - 1 : 0; + localVersion = new(localVersion.Major, localVersion.Minor, buildToUse); + } + var minimumVersion = Version.Parse(versionRecommendation.MinimumVersion); + if (localVersion < minimumVersion) + { + await _errorHandlingService.HandleError(nameof(NewVersionCheckService), nameof(CheckForNewVersion), "New version required", + "You need to update to the latest version as TSC won't work anymore", _issueKeys.NewRequiredSoftwareAvailable, null, null).ConfigureAwait(false); + await _errorHandlingService.HandleErrorResolved(_issueKeys.NewRecommendedSoftwareAvailable, null).ConfigureAwait(false); + await _errorHandlingService.HandleErrorResolved(_issueKeys.NewSoftwareAvailable, null).ConfigureAwait(false); + return; + } + else + { + await _errorHandlingService.HandleErrorResolved(_issueKeys.NewRequiredSoftwareAvailable, null).ConfigureAwait(false); + } + + var recommendedVersion = Version.Parse(versionRecommendation.RecommendedVersion); + if (localVersion < recommendedVersion) + { + var headLineText = versionRecommendation.RecommendedVersionRequiredInDays == default ? "New version recommended" : $"New Version required in {versionRecommendation.RecommendedVersionRequiredInDays} days"; + var messageText = versionRecommendation.RecommendedVersionRequiredInDays == default ? "It is recommended to update to the latest version" : $"After {versionRecommendation.RecommendedVersionRequiredInDays} days your current installed version won't work anymore. Please update as soon as possible."; + await _errorHandlingService.HandleError(nameof(NewVersionCheckService), nameof(CheckForNewVersion), headLineText, + messageText, _issueKeys.NewRecommendedSoftwareAvailable, null, null).ConfigureAwait(false); + await _errorHandlingService.HandleErrorResolved(_issueKeys.NewSoftwareAvailable, null).ConfigureAwait(false); + return; + } + else + { + await _errorHandlingService.HandleErrorResolved(_issueKeys.NewRecommendedSoftwareAvailable, null).ConfigureAwait(false); } - catch (Exception ex) + var latestVersion = Version.Parse(versionRecommendation.LatestVersion); + if (localVersion < latestVersion) { - _logger.LogError(ex, "Couldn't check for new version"); - _settings.IsNewVersionAvailable = false; + var headLineText = "New version available"; + var messageText = "Update to the latest version to get the latest new features"; + await _errorHandlingService.HandleError(nameof(NewVersionCheckService), nameof(CheckForNewVersion), headLineText, + messageText, _issueKeys.NewSoftwareAvailable, null, null).ConfigureAwait(false); return; } - _settings.IsNewVersionAvailable = false; + await _errorHandlingService.HandleErrorResolved(_issueKeys.NewSoftwareAvailable, null).ConfigureAwait(false); } private async Task GetRedirectedUrlAsync(string uri) diff --git a/TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs b/TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs index d26c97cbb..cabf59c04 100644 --- a/TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs +++ b/TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs @@ -9,7 +9,6 @@ public interface ISettings int? HomeBatterySoc { get; set; } int? HomeBatteryPower { get; set; } bool ControlledACarAtLastCycle { get; set; } - bool IsNewVersionAvailable { get; set; } DateTimeOffset LastPvValueUpdate { get; set; } int? AverageHomeGridVoltage { get; set; } bool CrashedOnStartup { get; set; } diff --git a/TeslaSolarCharger/Shared/Dtos/Settings/Settings.cs b/TeslaSolarCharger/Shared/Dtos/Settings/Settings.cs index e8f43835e..70a165d7b 100644 --- a/TeslaSolarCharger/Shared/Dtos/Settings/Settings.cs +++ b/TeslaSolarCharger/Shared/Dtos/Settings/Settings.cs @@ -4,7 +4,6 @@ namespace TeslaSolarCharger.Shared.Dtos.Settings; public class Settings : ISettings { - public bool IsNewVersionAvailable { get; set; } public int? InverterPower { get; set; } public int? Overage { get; set; } public List CarsToManage => Cars.Where(c => c.ShouldBeManaged == true).OrderBy(c => c.ChargingPriority).ToList();