Skip to content

Commit

Permalink
feat(NewVersionCheckService): can display different messages based on…
Browse files Browse the repository at this point in the history
… required, recommened or latest version
  • Loading branch information
pkuehnel committed Jan 18, 2025
1 parent cfcc568 commit 165d9fe
Show file tree
Hide file tree
Showing 12 changed files with 102 additions and 54 deletions.
Original file line number Diff line number Diff line change
@@ -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;
}
7 changes: 5 additions & 2 deletions TeslaSolarCharger/Server/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ async Task DoStartupStuff(WebApplication webApplication, ILogger<Program> logger
var teslaSolarChargerContext = webApplication.Services.GetRequiredService<ITeslaSolarChargerContext>();
await teslaSolarChargerContext.Database.MigrateAsync().ConfigureAwait(false);

var errorHandlingService = webApplication.Services.GetRequiredService<IErrorHandlingService>();
await errorHandlingService.RemoveInvalidLoggedErrorsAsync().ConfigureAwait(false);


var teslaFleetApiService = webApplication.Services.GetRequiredService<ITeslaFleetApiService>();
await teslaFleetApiService.RefreshFleetApiRequestsAreAllowed();

Expand Down Expand Up @@ -223,11 +227,10 @@ async Task DoStartupStuff(WebApplication webApplication, ILogger<Program> logger
{
await jobManager.StartJobs().ConfigureAwait(false);
}
var errorHandlingService = webApplication.Services.GetRequiredService<IErrorHandlingService>();

var issueKeys = webApplication.Services.GetRequiredService<IIssueKeys>();
await errorHandlingService.HandleErrorResolved(issueKeys.CrashedOnStartup, null)
.ConfigureAwait(false);
await errorHandlingService.RemoveInvalidLoggedErrorsAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,16 @@ public class PossibleIssues(IIssueKeys issueKeys) : IPossibleIssues
{
private readonly Dictionary<string, DtoIssue> _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,
Expand All @@ -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,
Expand Down
17 changes: 9 additions & 8 deletions TeslaSolarCharger/Server/Services/BackendApiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -163,8 +164,7 @@ internal string GenerateAuthUrl(DtoTeslaOAuthRequestInformation oAuthInformation
return url;
}

public async Task PostInstallationInformation(string reason)

public async Task<DtoVersionRecommendation> PostInstallationInformation(string reason)
{
try
{
Expand All @@ -177,33 +177,34 @@ public async Task PostInstallationInformation(string reason)
if (tokenState == TokenState.UpToDate)
{
var token = await teslaSolarChargerContext.BackendTokens.SingleAsync();
var result = await SendRequestToBackend<object>(HttpMethod.Post, token.AccessToken,
var result = await SendRequestToBackend<DtoVersionRecommendation>(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<object>(HttpMethod.Post, null,
var noTokenResult = await SendRequestToBackend<DtoVersionRecommendation>(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<string?> GetCurrentVersion()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using TeslaSolarCharger.Model.Entities.TeslaSolarCharger;
using TeslaSolarCharger.Server.Dtos.Solar4CarBackend;
using TeslaSolarCharger.Shared.Dtos;

namespace TeslaSolarCharger.Server.Services.Contracts;

public interface IBackendApiService
{
Task<DtoValue<string>> StartTeslaOAuth(string locale, string baseUrl);
Task PostInstallationInformation(string reason);
Task<DtoVersionRecommendation> PostInstallationInformation(string reason);
Task<string?> GetCurrentVersion();
Task GetNewBackendNotifications();
Task GetToken(DtoBackendLogin login);
Expand Down
3 changes: 0 additions & 3 deletions TeslaSolarCharger/Server/Services/ErrorHandlingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
86 changes: 52 additions & 34 deletions TeslaSolarCharger/Server/Services/NewVersionCheckService.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<NewVersionCheckService> 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<string?> GetRedirectedUrlAsync(string uri)
Expand Down
1 change: 0 additions & 1 deletion TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
1 change: 0 additions & 1 deletion TeslaSolarCharger/Shared/Dtos/Settings/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<DtoCar> CarsToManage => Cars.Where(c => c.ShouldBeManaged == true).OrderBy(c => c.ChargingPriority).ToList();
Expand Down

0 comments on commit 165d9fe

Please sign in to comment.