diff --git a/TeslaSolarCharger/Client/Components/BackendIssueValidation.razor b/TeslaSolarCharger/Client/Components/BackendIssueValidation.razor index 4d7a860cc..8b05e2091 100644 --- a/TeslaSolarCharger/Client/Components/BackendIssueValidation.razor +++ b/TeslaSolarCharger/Client/Components/BackendIssueValidation.razor @@ -11,7 +11,12 @@

Issues:

@foreach (var issue in _issues) { -
+
"yellow", + _ => "white", + })">
@issue.IssueMessage
@if (issue.PossibleSolutions.Length > 0) { diff --git a/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs b/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs index 9de799882..4b82c9367 100644 --- a/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs +++ b/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs @@ -1,4 +1,5 @@ -using TeslaSolarCharger.Shared.Dtos.BaseConfiguration; +using Microsoft.AspNetCore.Mvc; +using TeslaSolarCharger.Shared.Dtos.BaseConfiguration; namespace TeslaSolarCharger.Server.Contracts; @@ -7,4 +8,5 @@ public interface IBaseConfigurationService Task UpdateBaseConfigurationAsync(DtoBaseConfiguration baseConfiguration); Task UpdateMaxCombinedCurrent(int? maxCombinedCurrent); void UpdatePowerBuffer(int powerBuffer); + Task DownloadBackup(); } diff --git a/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs b/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs index 7ae92209b..239f5a0e3 100644 --- a/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs +++ b/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs @@ -31,5 +31,12 @@ public void UpdateMaxCombinedCurrent(int? maxCombinedCurrent) => [HttpGet] public void UpdatePowerBuffer(int powerBuffer) => _service.UpdatePowerBuffer(powerBuffer); + + [HttpGet] + public async Task DownloadBackup() + { + var bytes = await _service.DownloadBackup().ConfigureAwait(false); + return File(bytes, "application/zip", "TSCBackup.zip"); + } } } diff --git a/TeslaSolarCharger/Server/Resources/PossibleIssues/IssueKeys.cs b/TeslaSolarCharger/Server/Resources/PossibleIssues/IssueKeys.cs index e94054d7b..fbbe6ac69 100644 --- a/TeslaSolarCharger/Server/Resources/PossibleIssues/IssueKeys.cs +++ b/TeslaSolarCharger/Server/Resources/PossibleIssues/IssueKeys.cs @@ -19,4 +19,5 @@ public class IssueKeys public string VersionNotUpToDate = "VersionNotUpToDate"; public string CorrectionFactorZero = "CorrectionFactorZero"; public string ServerTimeZoneDifferentFromClient = "ServerTimeZoneDifferentFromClient"; + public string NewTeslaApi = "NewTeslaApi"; } diff --git a/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs b/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs index 8e5eca864..a25fdb6f0 100644 --- a/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs +++ b/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs @@ -134,6 +134,13 @@ public PossibleIssues(IssueKeys issueKeys) "Update the TimeZone of the TeslaSolarChargerContainer in your docker-compose.yml." ) }, + { + issueKeys.NewTeslaApi, CreateIssue("New cars are currently not supported", + IssueType.Information, + "If there are any updates on this topic, I will post them here.", + "Sorry for this information beeing not removable - it will be possible in a future update." + ) + }, }; } diff --git a/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs b/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs index e6f720226..27178b6a9 100644 --- a/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs +++ b/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs @@ -1,9 +1,16 @@ -using TeslaSolarCharger.Server.Contracts; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.Sqlite; +using Microsoft.Net.Http.Headers; +using System.IO.Compression; +using System.Net; +using TeslaSolarCharger.Model.Contracts; +using TeslaSolarCharger.Server.Contracts; using TeslaSolarCharger.Server.Scheduling; using TeslaSolarCharger.Shared.Contracts; using TeslaSolarCharger.Shared.Dtos.BaseConfiguration; using TeslaSolarCharger.Shared.Dtos.Contracts; using TeslaSolarCharger.Shared.Enums; +using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue; namespace TeslaSolarCharger.Server.Services; @@ -16,10 +23,11 @@ public class BaseConfigurationService : IBaseConfigurationService private readonly ISolarMqttService _solarMqttService; private readonly ISettings _settings; private readonly IPvValueService _pvValueService; + private readonly IDbConnectionStringHelper _dbConnectionStringHelper; public BaseConfigurationService(ILogger logger, IConfigurationWrapper configurationWrapper, JobManager jobManager, ITeslaMateMqttService teslaMateMqttService, ISolarMqttService solarMqttService, - ISettings settings, IPvValueService pvValueService) + ISettings settings, IPvValueService pvValueService, IDbConnectionStringHelper dbConnectionStringHelper) { _logger = logger; _configurationWrapper = configurationWrapper; @@ -28,6 +36,7 @@ public BaseConfigurationService(ILogger logger, IConfi _solarMqttService = solarMqttService; _settings = settings; _pvValueService = pvValueService; + _dbConnectionStringHelper = dbConnectionStringHelper; } public async Task UpdateBaseConfigurationAsync(DtoBaseConfiguration baseConfiguration) @@ -69,4 +78,59 @@ public void UpdatePowerBuffer(int powerBuffer) { _settings.PowerBuffer = powerBuffer; } + + public async Task DownloadBackup() + { + try + { + await _jobManager.StopJobs().ConfigureAwait(false); + + var backupCopyDestinationDirectory = _configurationWrapper.BackupCopyDestinationDirectory(); + if (Directory.Exists(backupCopyDestinationDirectory)) + { + Directory.Delete(backupCopyDestinationDirectory, true); + } + Directory.CreateDirectory(backupCopyDestinationDirectory); + + //Backup Sqlite database + using (var source = new SqliteConnection(_dbConnectionStringHelper.GetTeslaSolarChargerDbPath())) + using (var destination = new SqliteConnection(string.Format($"Data Source={Path.Combine(backupCopyDestinationDirectory, _configurationWrapper.GetSqliteFileNameWithoutPath())};Pooling=False"))) + { + source.Open(); + destination.Open(); + source.BackupDatabase(destination, "main", "main"); + } + + //Backup config files + var baseConfigFileFullName = _configurationWrapper.BaseConfigFileFullName(); + var carConfigFileFullName = _configurationWrapper.CarConfigFileFullName(); + File.Copy(baseConfigFileFullName, Path.Combine(backupCopyDestinationDirectory, Path.GetFileName(baseConfigFileFullName)), true); + File.Copy(carConfigFileFullName, Path.Combine(backupCopyDestinationDirectory, Path.GetFileName(carConfigFileFullName)), true); + + + var backupFileName = "TSC-Backup.zip"; + var backupZipDirectory = _configurationWrapper.BackupZipDirectory(); + if(Directory.Exists(backupZipDirectory)) + { + Directory.Delete(backupZipDirectory, true); + } + Directory.CreateDirectory(backupZipDirectory); + var destinationArchiveFileName = Path.Combine(backupZipDirectory, backupFileName); + ZipFile.CreateFromDirectory(backupCopyDestinationDirectory, destinationArchiveFileName); + var bytes = await File.ReadAllBytesAsync(destinationArchiveFileName).ConfigureAwait(false); + return bytes; + } + catch (Exception ex) + { + _logger.LogError(ex, "Couldn't download backup"); + throw; + } + finally + { + await _jobManager.StartJobs().ConfigureAwait(false); + } + + + + } } diff --git a/TeslaSolarCharger/Server/Services/IssueValidationService.cs b/TeslaSolarCharger/Server/Services/IssueValidationService.cs index a8cf7b99d..1d6c5eee8 100644 --- a/TeslaSolarCharger/Server/Services/IssueValidationService.cs +++ b/TeslaSolarCharger/Server/Services/IssueValidationService.cs @@ -43,7 +43,10 @@ public IssueValidationService(ILogger logger, ISettings public async Task> RefreshIssues(TimeSpan clientTimeZoneId) { _logger.LogTrace("{method}()", nameof(RefreshIssues)); - var issueList = new List(); + var issueList = new List + { + _possibleIssues.GetIssueByKey(_issueKeys.NewTeslaApi), + }; issueList.AddRange(GetServerConfigurationIssues(clientTimeZoneId)); if (Debugger.IsAttached) { diff --git a/TeslaSolarCharger/Server/appsettings.json b/TeslaSolarCharger/Server/appsettings.json index 58de41eb7..e94baeeba 100644 --- a/TeslaSolarCharger/Server/appsettings.json +++ b/TeslaSolarCharger/Server/appsettings.json @@ -40,6 +40,8 @@ }, "AllowedHosts": "*", "ConfigFileLocation": "configs", + "BackupCopyDestinationDirectory": "backups", + "BackupZipDirectory": "backupZips", "CarConfigFilename": "carConfig.json", "BaseConfigFileName": "baseConfig.json", "SqliteFileName": "TeslaSolarCharger.db", diff --git a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs index 4e56e570f..67e3897d5 100644 --- a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs @@ -83,4 +83,7 @@ public interface IConfigurationWrapper bool AllowCors(); bool ShouldDisplayApiRequestCounter(); bool ShouldIgnoreSslErrors(); + string BackupCopyDestinationDirectory(); + string GetSqliteFileNameWithoutPath(); + string BackupZipDirectory(); } diff --git a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs index a27544105..491e5bd77 100644 --- a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs @@ -40,13 +40,37 @@ public string CarConfigFileFullName() return Path.Combine(configFileDirectory, value); } + public string BackupCopyDestinationDirectory() + { + var configFileDirectory = ConfigFileDirectory(); + var environmentVariableName = "BackupCopyDestinationDirectory"; + var value = GetNotNullableConfigurationValue(environmentVariableName); + _logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); + return Path.Combine(configFileDirectory, value); + } + + public string BackupZipDirectory() + { + var configFileDirectory = ConfigFileDirectory(); + var environmentVariableName = "BackupZipDirectory"; + var value = GetNotNullableConfigurationValue(environmentVariableName); + _logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); + return Path.Combine(configFileDirectory, value); + } + public string SqliteFileFullName() { var configFileDirectory = ConfigFileDirectory(); + var value = GetSqliteFileNameWithoutPath(); + return Path.Combine(configFileDirectory, value); + } + + public string GetSqliteFileNameWithoutPath() + { var environmentVariableName = "SqliteFileName"; var value = GetNotNullableConfigurationValue(environmentVariableName); _logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); - return Path.Combine(configFileDirectory, value); + return value; } public string BaseConfigFileFullName()