From 3d85251b50645db49296a6f99297cbf535c8a4d0 Mon Sep 17 00:00:00 2001 From: "George Njeri (Swagfin)" Date: Sun, 13 Apr 2025 13:24:34 +0300 Subject: [PATCH 1/7] chore: nuget updates --- .../SemanticBackup.Infrastructure.csproj | 3 +-- SemanticBackup/Pages/Shared/_Layout.cshtml | 2 +- SemanticBackup/Properties/launchSettings.json | 2 +- SemanticBackup/SemanticBackup.csproj | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/SemanticBackup.Infrastructure/SemanticBackup.Infrastructure.csproj b/SemanticBackup.Infrastructure/SemanticBackup.Infrastructure.csproj index 34d6577..5b9181b 100644 --- a/SemanticBackup.Infrastructure/SemanticBackup.Infrastructure.csproj +++ b/SemanticBackup.Infrastructure/SemanticBackup.Infrastructure.csproj @@ -8,7 +8,7 @@ - + @@ -18,7 +18,6 @@ - diff --git a/SemanticBackup/Pages/Shared/_Layout.cshtml b/SemanticBackup/Pages/Shared/_Layout.cshtml index b760a63..f2ac859 100644 --- a/SemanticBackup/Pages/Shared/_Layout.cshtml +++ b/SemanticBackup/Pages/Shared/_Layout.cshtml @@ -68,7 +68,7 @@ @{ foreach (ResourceGroup directory in allResourceGroups) { - + } } diff --git a/SemanticBackup/Properties/launchSettings.json b/SemanticBackup/Properties/launchSettings.json index 212bb61..44c9ade 100644 --- a/SemanticBackup/Properties/launchSettings.json +++ b/SemanticBackup/Properties/launchSettings.json @@ -13,7 +13,7 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "https://localhost:5001;http://localhost:5000" + "applicationUrl": "https://localhost:6001;http://localhost:6000" }, "Docker": { "commandName": "Docker", diff --git a/SemanticBackup/SemanticBackup.csproj b/SemanticBackup/SemanticBackup.csproj index a4448a2..ce0296d 100644 --- a/SemanticBackup/SemanticBackup.csproj +++ b/SemanticBackup/SemanticBackup.csproj @@ -9,7 +9,7 @@ - + From 7d6c6eb55eab957834ab0d8b2afe95bb0858217f Mon Sep 17 00:00:00 2001 From: "George Njeri (Swagfin)" Date: Sun, 13 Apr 2025 13:45:49 +0300 Subject: [PATCH 2/7] fixed: resource group not deleting --- .../Implementations/ResourceGroupRepositoryLiteDb.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/SemanticBackup.Infrastructure/Implementations/ResourceGroupRepositoryLiteDb.cs b/SemanticBackup.Infrastructure/Implementations/ResourceGroupRepositoryLiteDb.cs index 09c670f..1751f55 100644 --- a/SemanticBackup.Infrastructure/Implementations/ResourceGroupRepositoryLiteDb.cs +++ b/SemanticBackup.Infrastructure/Implementations/ResourceGroupRepositoryLiteDb.cs @@ -50,14 +50,13 @@ public async Task VerifyByIdOrKeyThrowIfNotExistAsync(string reso return await GetByIdOrKeyAsync(resourceGroupIdentifier) ?? throw new Exception($"resource group not found with provided identity: {resourceGroupIdentifier}"); } - public async Task RemoveAsync(string id) + public async Task RemoveAsync(string resourceGroupIdentifier) { - var collection = _db.GetCollection(); - var objFound = await collection.Query().Where(x => x.Id == id).FirstOrDefaultAsync(); + ResourceGroup objFound = await GetByIdOrKeyAsync(resourceGroupIdentifier); if (objFound != null) { - bool success = await collection.DeleteAsync(new BsonValue(objFound.Id)); - await TryDeleteAllResourcesForGroupAsync(id); + bool success = await _db.GetCollection().DeleteAsync(new BsonValue(objFound.Id)); + await TryDeleteAllResourcesForGroupAsync(objFound.Id); return success; } return false; From 9929afb52c99bf920fcb5dc2ef7a4e7cc6c73b98 Mon Sep 17 00:00:00 2001 From: "George Njeri (Swagfin)" Date: Sun, 13 Apr 2025 15:58:44 +0300 Subject: [PATCH 3/7] chore: modified bots --- SemanticBackup.Core/Models/BackupRecord.cs | 6 +- .../BackgroundJobs/BackupBackgroundJob.cs | 94 ++++----- .../BackgroundJobs/BackupBackgroundZIPJob.cs | 75 ++++--- ...ckupRecordDeliveryDispatchBackgroundJob.cs | 199 +++++++++--------- ...kupRecordDeliverySchedulerBackgroundJob.cs | 2 +- .../BackupSchedulerBackgroundJob.cs | 119 +++++------ .../BackgroundJobs/Bots/BackupZippingBot.cs | 104 +++++---- .../Bots/InDepthDeleteDropboxBot.cs | 52 ++--- .../BackgroundJobs/Bots/MySQLBackupBot.cs | 96 ++++----- .../BackgroundJobs/Bots/SQLBackupBot.cs | 96 ++++----- .../BackgroundJobs/Bots/SQLRestoreBot.cs | 94 --------- .../Bots/UploaderAzureStorageBot.cs | 100 ++++----- .../BackgroundJobs/Bots/UploaderDropboxBot.cs | 97 +++++---- .../Bots/UploaderEmailSMTPBot.cs | 162 +++++++------- .../BackgroundJobs/Bots/UploaderFTPBot.cs | 118 +++++------ .../BackgroundJobs/Bots/UploaderLinkGenBot.cs | 81 ++++--- .../BackgroundJobs/Bots/_IBot.cs | 31 ++- .../BotsManagerBackgroundJob.cs | 4 +- .../BackgroundJobs/RestoreBackgroundJob.cs | 101 --------- .../BackupRecordRepositoryLiteDb.cs | 2 +- .../ResourceGroupRepositoryLiteDb.cs | 2 +- .../DatabaseBackups/Details.cshtml.cs | 6 +- .../Databases/Details.cshtml.cs | 4 +- .../Services/StatusNotificationService.cs | 4 +- .../DashboardRefreshHubDispatcher.cs | 2 +- 25 files changed, 704 insertions(+), 947 deletions(-) delete mode 100644 SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLRestoreBot.cs delete mode 100644 SemanticBackup.Infrastructure/BackgroundJobs/RestoreBackgroundJob.cs diff --git a/SemanticBackup.Core/Models/BackupRecord.cs b/SemanticBackup.Core/Models/BackupRecord.cs index 40ea01d..7cd1ae2 100644 --- a/SemanticBackup.Core/Models/BackupRecord.cs +++ b/SemanticBackup.Core/Models/BackupRecord.cs @@ -10,7 +10,7 @@ public class BackupRecord [Required] public string BackupDatabaseInfoId { get; set; } public string Name { get; set; } - public string BackupStatus { get; set; } = BackupRecordBackupStatus.QUEUED.ToString(); + public string BackupStatus { get; set; } = BackupRecordStatus.QUEUED.ToString(); [Required] public string Path { get; set; } public DateTime StatusUpdateDateUTC { get; set; } = DateTime.UtcNow; @@ -24,7 +24,8 @@ public class BackupRecord public string RestoreExecutionMessage { get; set; } = string.Empty; public string RestoreConfirmationToken { get; set; } = string.Empty; } - public enum BackupRecordBackupStatus + + public enum BackupRecordStatus { QUEUED, EXECUTING, @@ -33,6 +34,7 @@ public enum BackupRecordBackupStatus READY, ERROR } + public enum BackupRecordExecutedDeliveryRunStatus { PENDING_EXECUTION, diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs index abae411..5e0ffd9 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs @@ -48,7 +48,7 @@ private void SetupBackgroundService(CancellationToken cancellationToken) while (!cancellationToken.IsCancellationRequested) { //Await - await Task.Delay(5000); + await Task.Delay(5000, cancellationToken); try { using (var scope = _serviceScopeFactory.CreateScope()) @@ -58,10 +58,10 @@ private void SetupBackgroundService(CancellationToken cancellationToken) IDatabaseInfoRepository databaseInfoPersistanceService = scope.ServiceProvider.GetRequiredService(); IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); //Proceed - List queuedBackups = await backupRecordPersistanceService.GetAllByStatusAsync(BackupRecordBackupStatus.QUEUED.ToString()); + List queuedBackups = await backupRecordPersistanceService.GetAllByStatusAsync(BackupRecordStatus.QUEUED.ToString()); if (queuedBackups != null && queuedBackups.Count > 0) { - List scheduleToDelete = new List(); + List scheduleToDelete = []; foreach (BackupRecord backupRecord in queuedBackups.OrderBy(x => x.RegisteredDateUTC).ToList()) { _logger.LogInformation($"Processing Queued Backup Record Key: #{backupRecord.Id}..."); @@ -91,7 +91,7 @@ private void SetupBackgroundService(CancellationToken cancellationToken) else throw new Exception($"No Bot is registered to Handle Database Backups of Type: {resourceGroup.DbType}"); //Finally Update Status - bool updated = await backupRecordPersistanceService.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordBackupStatus.EXECUTING.ToString()); + bool updated = await backupRecordPersistanceService.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordStatus.EXECUTING.ToString()); if (updated) _logger.LogInformation($"Processing Queued Backup Record Key: #{backupRecord.Id}...SUCCESS"); else @@ -127,32 +127,26 @@ private void SetupBackgroundRemovedExpiredBackupsService(CancellationToken cance { while (!cancellationToken.IsCancellationRequested) { -#if DEBUG await Task.Delay(3000); //Runs After 3sec -#else - await Task.Delay(60000); //Runs After 1 Minute -#endif try { - using (var scope = _serviceScopeFactory.CreateScope()) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + //DI Injections + IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + //Proceed + List expiredBackups = await backupRecordPersistanceService.GetAllExpiredAsync(); + if (expiredBackups != null && expiredBackups.Count > 0) { - //DI Injections - IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - //Proceed - List expiredBackups = await backupRecordPersistanceService.GetAllExpiredAsync(); - if (expiredBackups != null && expiredBackups.Count > 0) - { - foreach (BackupRecord rm in expiredBackups.Take(50).ToList()) - if (!await backupRecordPersistanceService.RemoveAsync(rm.Id)) - _logger.LogWarning($"Unable to delete Expired Backup Record: {rm.Id}"); - else - { - _logger.LogInformation($"Removed Expired Backup Record, Id: {rm.Id}"); - //Options InDepth Delete - if (_persistanceOptions.InDepthBackupRecordDeleteEnabled) - await StartInDepthDeleteForAsync(rm); - } - } + foreach (BackupRecord rm in expiredBackups.Take(50).ToList()) + if (!await backupRecordPersistanceService.RemoveAsync(rm.Id)) + _logger.LogWarning($"Unable to delete Expired Backup Record: {rm.Id}"); + else + { + _logger.LogInformation($"Removed Expired Backup Record, Id: {rm.Id}"); + //Options InDepth Delete + if (_persistanceOptions.InDepthBackupRecordDeleteEnabled) + await StartInDepthDeleteForAsync(rm); + } } } catch (Exception ex) @@ -170,33 +164,31 @@ private async Task StartInDepthDeleteForAsync(BackupRecord rm) { if (rm == null) return; //scope - using (var scope = _serviceScopeFactory.CreateScope()) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); + IContentDeliveryRecordRepository contentDeliveryRecordsService = scope.ServiceProvider.GetRequiredService(); + BotsManagerBackgroundJob botsManagerBackgroundJob = scope.ServiceProvider.GetRequiredService(); + IDatabaseInfoRepository databaseInfoRepository = scope.ServiceProvider.GetRequiredService(); + //get db information + BackupDatabaseInfo backupRecordDbInfo = await databaseInfoRepository.GetByIdAsync(rm.BackupDatabaseInfoId); + //Check if valid Resource Group + ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupRecordDbInfo?.ResourceGroupId ?? string.Empty); + if (resourceGroup == null) + return; + //Proceed + List dbRecords = await contentDeliveryRecordsService.GetAllByBackupRecordIdAsync(rm.Id); //database record content delivery + if (dbRecords == null) + return; + List supportedInDepthDelete = [BackupDeliveryConfigTypes.Dropbox.ToString(), BackupDeliveryConfigTypes.AzureBlobStorage.ToString()]; + List supportedDeliveryRecords = [.. dbRecords.Where(x => supportedInDepthDelete.Contains(x.DeliveryType))]; + if (supportedDeliveryRecords == null || supportedDeliveryRecords.Count == 0) + return; + foreach (BackupRecordDelivery rec in supportedDeliveryRecords) { - IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); - IContentDeliveryRecordRepository contentDeliveryRecordsService = scope.ServiceProvider.GetRequiredService(); - BotsManagerBackgroundJob botsManagerBackgroundJob = scope.ServiceProvider.GetRequiredService(); - IDatabaseInfoRepository databaseInfoRepository = scope.ServiceProvider.GetRequiredService(); - //get db information - BackupDatabaseInfo backupRecordDbInfo = await databaseInfoRepository.GetByIdAsync(rm.BackupDatabaseInfoId); - //Check if valid Resource Group - ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupRecordDbInfo?.ResourceGroupId ?? string.Empty); - if (resourceGroup == null) - return; - //Proceed - var dbRecords = await contentDeliveryRecordsService.GetAllByBackupRecordIdAsync(rm.Id); //database record content delivery - if (dbRecords == null) - return; - List supportedInDepthDelete = new List { BackupDeliveryConfigTypes.Dropbox.ToString(), BackupDeliveryConfigTypes.AzureBlobStorage.ToString() }; - List supportedDeliveryRecords = dbRecords.Where(x => supportedInDepthDelete.Contains(x.DeliveryType)).ToList(); - if (supportedDeliveryRecords == null || supportedDeliveryRecords.Count == 0) - return; - foreach (var rec in supportedDeliveryRecords) + if (rec.DeliveryType == BackupDeliveryConfigTypes.Dropbox.ToString()) { - if (rec.DeliveryType == BackupDeliveryConfigTypes.Dropbox.ToString()) - { - //In Depth Remove From DropBox - botsManagerBackgroundJob.AddBot(new InDepthDeleteDropboxBot(resourceGroup, rm, rec, _serviceScopeFactory)); - } + //In Depth Remove From DropBox + botsManagerBackgroundJob.AddBot(new InDepthDeleteDropboxBot(resourceGroup, rm, rec, _serviceScopeFactory)); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundZIPJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundZIPJob.cs index e6bdfbe..d700a36 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundZIPJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundZIPJob.cs @@ -42,60 +42,58 @@ public Task StopAsync(CancellationToken cancellationToken) private void SetupBackgroundService(CancellationToken cancellationToken) { - var t = new Thread(async () => + Thread t = new Thread(async () => { while (!cancellationToken.IsCancellationRequested) { - await Task.Delay(7000); + await Task.Delay(7000, cancellationToken); try { - using (var scope = _serviceScopeFactory.CreateScope()) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + //DI INJECTIONS + IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); + IDatabaseInfoRepository databaseInfoRepository = scope.ServiceProvider.GetRequiredService(); + //Proceed + List queuedBackups = await backupRecordPersistanceService.GetAllByStatusAsync(BackupRecordStatus.COMPLETED.ToString()); + if (queuedBackups != null && queuedBackups.Count > 0) { - //DI INJECTIONS - IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); - IDatabaseInfoRepository databaseInfoRepository = scope.ServiceProvider.GetRequiredService(); - //Proceed - List queuedBackups = await backupRecordPersistanceService.GetAllByStatusAsync(BackupRecordBackupStatus.COMPLETED.ToString()); - if (queuedBackups != null && queuedBackups.Count > 0) + foreach (BackupRecord backupRecord in queuedBackups.OrderBy(x => x.RegisteredDateUTC).ToList()) { - foreach (BackupRecord backupRecord in queuedBackups.OrderBy(x => x.RegisteredDateUTC).ToList()) + //get valid database + BackupDatabaseInfo backupRecordDbInfo = await databaseInfoRepository.GetByIdAsync(backupRecord.BackupDatabaseInfoId); + //Check if valid Resource Group + ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupRecordDbInfo?.ResourceGroupId ?? string.Empty); + if (resourceGroup != null) { - //get valid database - BackupDatabaseInfo backupRecordDbInfo = await databaseInfoRepository.GetByIdAsync(backupRecord.BackupDatabaseInfoId); - //Check if valid Resource Group - ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupRecordDbInfo?.ResourceGroupId ?? string.Empty); - if (resourceGroup != null) + //Use Resource Group Threads + if (resourceGroup.CompressBackupFiles) { - //Use Resource Group Threads - if (resourceGroup.CompressBackupFiles) + //Check Resource Group Maximum Threads + if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, resourceGroup.MaximumRunningBots)) { - //Check Resource Group Maximum Threads - if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, resourceGroup.MaximumRunningBots)) - { - _logger.LogInformation($"Queueing Zip Database Record Key: #{backupRecord.Id}..."); - //Add to Queue - _botsManagerBackgroundJob.AddBot(new BackupZippingRobot(resourceGroup.Id, backupRecord, _serviceScopeFactory)); - bool updated = await backupRecordPersistanceService.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordBackupStatus.COMPRESSING.ToString()); - if (updated) - _logger.LogInformation($"Queueing Zip Database Record Key: #{backupRecord.Id}...SUCCESS"); - else - _logger.LogWarning($"Queued for Zipping But Failed to Update Status for Backup Record Key: #{backupRecord.Id}"); - } - } - else - { - _logger.LogInformation($">> Skipping Compression for Database Record Key: #{backupRecord.Id}..."); - bool updated = await backupRecordPersistanceService.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordBackupStatus.READY.ToString()); + _logger.LogInformation($"Queueing Zip Database Record Key: #{backupRecord.Id}..."); + //Add to Queue + _botsManagerBackgroundJob.AddBot(new BackupZippingRobot(resourceGroup.Id, backupRecord, _serviceScopeFactory)); + bool updated = await backupRecordPersistanceService.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordStatus.COMPRESSING.ToString()); if (updated) - _logger.LogInformation($">> Skipped Compression and Completed Backup Updated Record Key: #{backupRecord.Id}...SUCCESS"); + _logger.LogInformation($"Queueing Zip Database Record Key: #{backupRecord.Id}...SUCCESS"); else - _logger.LogWarning($"Failed to Update Status as READY for Backup Record Key: #{backupRecord.Id}"); + _logger.LogWarning($"Queued for Zipping But Failed to Update Status for Backup Record Key: #{backupRecord.Id}"); } } else - _logger.LogWarning($"The Backup Record Id: {backupRecord.Id}, doesn't seem to have been assigned to a valid Resource Group, Zipping Skipped"); + { + _logger.LogInformation($">> Skipping Compression for Database Record Key: #{backupRecord.Id}..."); + bool updated = await backupRecordPersistanceService.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordStatus.READY.ToString()); + if (updated) + _logger.LogInformation($">> Skipped Compression and Completed Backup Updated Record Key: #{backupRecord.Id}...SUCCESS"); + else + _logger.LogWarning($"Failed to Update Status as READY for Backup Record Key: #{backupRecord.Id}"); + } } + else + _logger.LogWarning($"The Backup Record Id: {backupRecord.Id}, doesn't seem to have been assigned to a valid Resource Group, Zipping Skipped"); } } } @@ -103,7 +101,6 @@ private void SetupBackgroundService(CancellationToken cancellationToken) { _logger.LogError(ex.Message); } - } }); t.Start(); diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs index b270d0a..ea03165 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs @@ -20,9 +20,9 @@ public class BackupRecordDeliveryDispatchBackgroundJob : IHostedService public BackupRecordDeliveryDispatchBackgroundJob(ILogger logger, IServiceScopeFactory serviceScopeFactory, BotsManagerBackgroundJob botsManagerBackgroundJob) { - this._logger = logger; - this._serviceScopeFactory = serviceScopeFactory; - this._botsManagerBackgroundJob = botsManagerBackgroundJob; + _logger = logger; + _serviceScopeFactory = serviceScopeFactory; + _botsManagerBackgroundJob = botsManagerBackgroundJob; } public Task StartAsync(CancellationToken cancellationToken) @@ -40,101 +40,98 @@ public Task StopAsync(CancellationToken cancellationToken) private void SetupBackgroundService(CancellationToken cancellationToken) { - var t = new Thread(async () => + Thread t = new Thread(async () => { while (!cancellationToken.IsCancellationRequested) { - await Task.Delay(10000); + await Task.Delay(10000, cancellationToken); try { - using (var scope = _serviceScopeFactory.CreateScope()) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + //DI INJECTIONS + IContentDeliveryRecordRepository contentDeliveryRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); + IDatabaseInfoRepository databaseInfoPersistanceService = scope.ServiceProvider.GetRequiredService(); + //Proceed + List contentDeliveryRecords = await contentDeliveryRecordPersistanceService.GetAllByStatusAsync(BackupRecordDeliveryStatus.QUEUED.ToString()); + if (contentDeliveryRecords != null && contentDeliveryRecords.Count > 0) { - //DI INJECTIONS - IContentDeliveryRecordRepository contentDeliveryRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); - IDatabaseInfoRepository databaseInfoPersistanceService = scope.ServiceProvider.GetRequiredService(); - //Proceed - List contentDeliveryRecords = await contentDeliveryRecordPersistanceService.GetAllByStatusAsync(BackupRecordDeliveryStatus.QUEUED.ToString()); - if (contentDeliveryRecords != null && contentDeliveryRecords.Count > 0) + List scheduleToDeleteRecords = []; + foreach (BackupRecordDelivery contentDeliveryRecord in contentDeliveryRecords.OrderBy(x => x.RegisteredDateUTC).ToList()) { - List scheduleToDeleteRecords = new List(); - foreach (BackupRecordDelivery contentDeliveryRecord in contentDeliveryRecords.OrderBy(x => x.RegisteredDateUTC).ToList()) - { - _logger.LogInformation($"Processing Queued Content Delivery Record: #{contentDeliveryRecord.Id}..."); - BackupRecord backupRecordInfo = await backupRecordPersistanceService.GetByIdAsync(contentDeliveryRecord?.BackupRecordId ?? 0); - BackupDatabaseInfo backupDatabaseInfo = await databaseInfoPersistanceService.GetByIdAsync(backupRecordInfo?.BackupDatabaseInfoId); - ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupDatabaseInfo?.ResourceGroupId); + _logger.LogInformation($"Processing Queued Content Delivery Record: #{contentDeliveryRecord.Id}..."); + BackupRecord backupRecordInfo = await backupRecordPersistanceService.GetByIdAsync(contentDeliveryRecord?.BackupRecordId ?? 0); + BackupDatabaseInfo backupDatabaseInfo = await databaseInfoPersistanceService.GetByIdAsync(backupRecordInfo?.BackupDatabaseInfoId); + ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupDatabaseInfo?.ResourceGroupId); - if (backupRecordInfo == null) - { - _logger.LogWarning($"No Backup Record with Id: {contentDeliveryRecord.BackupRecordId}, Content Delivery Record will be Deleted: {contentDeliveryRecord.Id}"); - scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); - } - else if (resourceGroup == null) - { - _logger.LogWarning($"Backup Record Id: {contentDeliveryRecord.BackupRecordId}, Queued for Content Delivery has no valid Resource Group, Will be Removed"); - scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); - } - else if (resourceGroup.BackupDeliveryConfig == null) - { - _logger.LogWarning($"Backup Record Id: {contentDeliveryRecord.BackupRecordId}, Queued for Content Delivery has no valid Configuration, Will be Removed"); - scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); - } - else + if (backupRecordInfo == null) + { + _logger.LogWarning($"No Backup Record with Id: {contentDeliveryRecord.BackupRecordId}, Content Delivery Record will be Deleted: {contentDeliveryRecord.Id}"); + scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); + } + else if (resourceGroup == null) + { + _logger.LogWarning($"Backup Record Id: {contentDeliveryRecord.BackupRecordId}, Queued for Content Delivery has no valid Resource Group, Will be Removed"); + scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); + } + else if (resourceGroup.BackupDeliveryConfig == null) + { + _logger.LogWarning($"Backup Record Id: {contentDeliveryRecord.BackupRecordId}, Queued for Content Delivery has no valid Configuration, Will be Removed"); + scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); + } + else + { + //Override Maximum Running Threads// This is because of currently being used exception + if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, 1)) { - //Override Maximum Running Threads// This is because of currently being used exception - if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, 1)) + string status = BackupRecordDeliveryStatus.EXECUTING.ToString(); + string statusMsg = "Dispatching Backup Record"; + if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.DownloadLink.ToString()) { - string status = BackupRecordDeliveryStatus.EXECUTING.ToString(); - string statusMsg = "Dispatching Backup Record"; - if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.DownloadLink.ToString()) - { - //Download Link Generator - _botsManagerBackgroundJob.AddBot(new UploaderLinkGenBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Ftp.ToString()) - { - //FTP Uploader - _botsManagerBackgroundJob.AddBot(new UploaderFTPBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Smtp.ToString()) - { - //Email Send and Uploader - _botsManagerBackgroundJob.AddBot(new UploaderEmailSMTPBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Dropbox.ToString()) - { - //Email Send and Uploader - _botsManagerBackgroundJob.AddBot(new UploaderDropboxBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.AzureBlobStorage.ToString()) - { - //Azure Blob Storage - _botsManagerBackgroundJob.AddBot(new UploaderAzureStorageBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); - } - else - { - status = BackupRecordDeliveryStatus.ERROR.ToString(); - statusMsg = $"Backup Record Id: {contentDeliveryRecord.BackupRecordId}, Queued for Content Delivery has UNSUPPORTED Delivery Type, Record Will be Removed"; - _logger.LogWarning(statusMsg); - scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); - } - //Finally Update Status - bool updated = await contentDeliveryRecordPersistanceService.UpdateStatusFeedAsync(contentDeliveryRecord.Id, status, statusMsg); - if (!updated) - _logger.LogWarning($"Queued for Backup but was unable to update backup record Key: #{contentDeliveryRecord.Id} status"); + //Download Link Generator + _botsManagerBackgroundJob.AddBot(new UploaderLinkGenBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Ftp.ToString()) + { + //FTP Uploader + _botsManagerBackgroundJob.AddBot(new UploaderFTPBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Smtp.ToString()) + { + //Email Send and Uploader + _botsManagerBackgroundJob.AddBot(new UploaderEmailSMTPBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Dropbox.ToString()) + { + //Email Send and Uploader + _botsManagerBackgroundJob.AddBot(new UploaderDropboxBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.AzureBlobStorage.ToString()) + { + //Azure Blob Storage + _botsManagerBackgroundJob.AddBot(new UploaderAzureStorageBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); } else - _logger.LogInformation($"Resource Group With Id: {resourceGroup.Id} has Exceeded its Maximum Allocated Running Threads Count: {resourceGroup.MaximumRunningBots}"); + { + status = BackupRecordDeliveryStatus.ERROR.ToString(); + statusMsg = $"Backup Record Id: {contentDeliveryRecord.BackupRecordId}, Queued for Content Delivery has UNSUPPORTED Delivery Type, Record Will be Removed"; + _logger.LogWarning(statusMsg); + scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); + } + //Finally Update Status + bool updated = await contentDeliveryRecordPersistanceService.UpdateStatusFeedAsync(contentDeliveryRecord.Id, status, statusMsg); + if (!updated) + _logger.LogWarning($"Queued for Backup but was unable to update backup record Key: #{contentDeliveryRecord.Id} status"); } + else + _logger.LogInformation($"Resource Group With Id: {resourceGroup.Id} has Exceeded its Maximum Allocated Running Threads Count: {resourceGroup.MaximumRunningBots}"); } - //Check if Any Delete - if (scheduleToDeleteRecords.Count > 0) - foreach (var rm in scheduleToDeleteRecords) - await contentDeliveryRecordPersistanceService.RemoveAsync(rm); } - + //Check if Any Delete + if (scheduleToDeleteRecords.Count > 0) + foreach (var rm in scheduleToDeleteRecords) + await contentDeliveryRecordPersistanceService.RemoveAsync(rm); } } catch (Exception ex) @@ -149,31 +146,29 @@ private void SetupBackgroundService(CancellationToken cancellationToken) private void SetupBackgroundRemovedExpiredBackupsService(CancellationToken cancellationToken) { - var t = new Thread(async () => + Thread t = new Thread(async () => { while (!cancellationToken.IsCancellationRequested) { - await Task.Delay(60000); //Runs After 1 Minute + await Task.Delay(60000, cancellationToken); //Runs After 1 Minute try { - using (var scope = _serviceScopeFactory.CreateScope()) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + //DI INJECTIONS + IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + //Proceed + List expiredBackups = await backupRecordPersistanceService.GetAllExpiredAsync(); + if (expiredBackups != null && expiredBackups.Count > 0) { - //DI INJECTIONS - IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - //Proceed - List expiredBackups = await backupRecordPersistanceService.GetAllExpiredAsync(); - if (expiredBackups != null && expiredBackups.Count > 0) - { - List toDeleteList = new List(); - foreach (BackupRecord backupRecord in expiredBackups) - toDeleteList.Add(backupRecord.Id); - _logger.LogInformation($"Queued ({expiredBackups.Count}) Expired Records for Delete"); - //Check if Any Delete - if (toDeleteList.Count > 0) - foreach (var rm in toDeleteList) - if (!(await backupRecordPersistanceService.RemoveAsync(rm))) - _logger.LogWarning("Unable to delete Expired Backup Record"); - } + List toDeleteList = new List(); + foreach (BackupRecord backupRecord in expiredBackups) + toDeleteList.Add(backupRecord.Id); + _logger.LogInformation($"Queued ({expiredBackups.Count}) Expired Records for Delete"); + //Check if Any Delete + if (toDeleteList.Count > 0) + foreach (var rm in toDeleteList) + if (!(await backupRecordPersistanceService.RemoveAsync(rm))) + _logger.LogWarning("Unable to delete Expired Backup Record"); } } catch (Exception ex) diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliverySchedulerBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliverySchedulerBackgroundJob.cs index ae3d34c..a043a28 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliverySchedulerBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliverySchedulerBackgroundJob.cs @@ -41,7 +41,7 @@ private void SetupBackgroundService(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { - await Task.Delay(4000); + await Task.Delay(4000, cancellationToken); try { using (var scope = _serviceScopeFactory.CreateScope()) diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupSchedulerBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupSchedulerBackgroundJob.cs index 5c81f12..1ab0c90 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupSchedulerBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupSchedulerBackgroundJob.cs @@ -30,10 +30,6 @@ public BackupSchedulerBackgroundJob( this._persistanceOptions = persistanceOptions; this._serviceScopeFactory = serviceScopeFactory; this._botsManagerBackgroundJob = botsManagerBackgroundJob; - } - public void Initialize() - { - } public Task StartAsync(CancellationToken cancellationToken) { @@ -53,78 +49,75 @@ private void SetupBackgroundService(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { - await Task.Delay(3000); + await Task.Delay(3000, cancellationToken); try { - using (var scope = _serviceScopeFactory.CreateScope()) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + //DI INJECTIONS + IBackupScheduleRepository backupSchedulePersistanceService = scope.ServiceProvider.GetRequiredService(); + IDatabaseInfoRepository databaseInfoPersistanceService = scope.ServiceProvider.GetRequiredService(); + IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); + IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + //Proceed + DateTime currentTimeUTC = DateTime.UtcNow; + List dueSchedules = await backupSchedulePersistanceService.GetAllDueByDateAsync(); + if (dueSchedules != null && dueSchedules.Count > 0) { - //DI INJECTIONS - IBackupScheduleRepository backupSchedulePersistanceService = scope.ServiceProvider.GetRequiredService(); - IDatabaseInfoRepository databaseInfoPersistanceService = scope.ServiceProvider.GetRequiredService(); - IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); - IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - //Proceed - DateTime currentTimeUTC = DateTime.UtcNow; - List dueSchedules = await backupSchedulePersistanceService.GetAllDueByDateAsync(); - if (dueSchedules != null && dueSchedules.Count > 0) + List scheduleToDelete = new List(); + foreach (BackupSchedule schedule in dueSchedules.OrderBy(x => x.NextRunUTC).ToList()) { - List scheduleToDelete = new List(); - foreach (BackupSchedule schedule in dueSchedules.OrderBy(x => x.NextRunUTC).ToList()) + _logger.LogInformation($"Queueing Scheduled Backup..."); + BackupDatabaseInfo backupDatabaseInfo = await databaseInfoPersistanceService.GetByIdAsync(schedule.BackupDatabaseInfoId); + if (backupDatabaseInfo == null) { - _logger.LogInformation($"Queueing Scheduled Backup..."); - BackupDatabaseInfo backupDatabaseInfo = await databaseInfoPersistanceService.GetByIdAsync(schedule.BackupDatabaseInfoId); - if (backupDatabaseInfo == null) + _logger.LogWarning($"No Database Info matches with Id: {schedule.BackupDatabaseInfoId}, Schedule Record will be Deleted: {schedule.Id}"); + scheduleToDelete.Add(schedule.Id); + } + else + { + //Proceed + ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupDatabaseInfo.ResourceGroupId); + if (resourceGroup == null) { - _logger.LogWarning($"No Database Info matches with Id: {schedule.BackupDatabaseInfoId}, Schedule Record will be Deleted: {schedule.Id}"); + _logger.LogWarning($"Can NOT queue Database for Backup Id: {backupDatabaseInfo.Id}, Reason: Assigned Resource Group doen't exist, Resource Group Id: {backupDatabaseInfo.Id}, Schedule will be Removed"); scheduleToDelete.Add(schedule.Id); } else { - //Proceed - ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupDatabaseInfo.ResourceGroupId); - if (resourceGroup == null) - { - _logger.LogWarning($"Can NOT queue Database for Backup Id: {backupDatabaseInfo.Id}, Reason: Assigned Resource Group doen't exist, Resource Group Id: {backupDatabaseInfo.Id}, Schedule will be Removed"); - scheduleToDelete.Add(schedule.Id); - } - else + //has valid Resource Group Proceed + DateTime RecordExpiryUTC = currentTimeUTC.AddDays(resourceGroup.BackupExpiryAgeInDays); + BackupRecord newRecord = new BackupRecord { - //has valid Resource Group Proceed - DateTime RecordExpiryUTC = currentTimeUTC.AddDays(resourceGroup.BackupExpiryAgeInDays); - BackupRecord newRecord = new BackupRecord - { - BackupDatabaseInfoId = schedule.BackupDatabaseInfoId, - BackupStatus = BackupRecordBackupStatus.QUEUED.ToString(), - ExpiryDateUTC = RecordExpiryUTC, - Name = $"{backupDatabaseInfo.DatabaseName} on {resourceGroup.DbServer}", - Path = Path.Combine(_persistanceOptions.DefaultBackupDirectory, resourceGroup.GetSavingPathFromFormat(backupDatabaseInfo.DatabaseName, _persistanceOptions.BackupFileSaveFormat, currentTimeUTC)), - StatusUpdateDateUTC = currentTimeUTC, - RegisteredDateUTC = currentTimeUTC, - ExecutedDeliveryRun = false - }; - - bool addedSuccess = await backupRecordPersistanceService.AddOrUpdateAsync(newRecord); - if (!addedSuccess) - throw new Exception("Unable to Queue Database for Backup"); - else - _logger.LogInformation($"Queueing Scheduled Backup...SUCCESS"); - //Update Schedule - bool updatedSchedule = await backupSchedulePersistanceService.UpdateLastRunAsync(schedule.Id, currentTimeUTC); - if (!updatedSchedule) - _logger.LogWarning("Unable to Update Scheduled Next Run"); - //Buy Some Seconds to avoid Conflict Name - await Task.Delay(new Random().Next(100)); - } + BackupDatabaseInfoId = schedule.BackupDatabaseInfoId, + BackupStatus = BackupRecordStatus.QUEUED.ToString(), + ExpiryDateUTC = RecordExpiryUTC, + Name = $"{backupDatabaseInfo.DatabaseName} on {resourceGroup.DbServer}", + Path = Path.Combine(_persistanceOptions.DefaultBackupDirectory, resourceGroup.GetSavingPathFromFormat(backupDatabaseInfo.DatabaseName, _persistanceOptions.BackupFileSaveFormat, currentTimeUTC)), + StatusUpdateDateUTC = currentTimeUTC, + RegisteredDateUTC = currentTimeUTC, + ExecutedDeliveryRun = false + }; + bool addedSuccess = await backupRecordPersistanceService.AddOrUpdateAsync(newRecord); + if (!addedSuccess) + throw new Exception("Unable to Queue Database for Backup"); + else + _logger.LogInformation($"Queueing Scheduled Backup...SUCCESS"); + //Update Schedule + bool updatedSchedule = await backupSchedulePersistanceService.UpdateLastRunAsync(schedule.Id, currentTimeUTC); + if (!updatedSchedule) + _logger.LogWarning("Unable to Update Scheduled Next Run"); + //Buy Some Seconds to avoid Conflict Name + await Task.Delay(new Random().Next(100)); } } - //Check if Any Delete - if (scheduleToDelete.Count > 0) - foreach (var rm in scheduleToDelete) - await backupSchedulePersistanceService.RemoveAsync(rm); - } + } + //Check if Any Delete + if (scheduleToDelete.Count > 0) + foreach (var rm in scheduleToDelete) + await backupSchedulePersistanceService.RemoveAsync(rm); } } catch (Exception ex) @@ -140,7 +133,7 @@ private void SetupBackgroundNonResponsiveStopService(CancellationToken cancellat { var t = new Thread(async () => { - List statusChecks = new List { BackupRecordBackupStatus.EXECUTING.ToString(), BackupRecordBackupStatus.COMPRESSING.ToString(), BackupRecordDeliveryStatus.EXECUTING.ToString() }; + List statusChecks = new List { BackupRecordStatus.EXECUTING.ToString(), BackupRecordStatus.COMPRESSING.ToString(), BackupRecordDeliveryStatus.EXECUTING.ToString() }; int executionTimeoutInMinutes = _persistanceOptions.ExecutionTimeoutInMinutes < 1 ? 1 : _persistanceOptions.ExecutionTimeoutInMinutes; while (!cancellationToken.IsCancellationRequested) { @@ -158,7 +151,7 @@ private void SetupBackgroundNonResponsiveStopService(CancellationToken cancellat if (recordsIds != null && recordsIds.Count > 0) foreach (long id in recordsIds) { - await backupRecordPersistanceService.UpdateStatusFeedAsync(id, BackupRecordBackupStatus.ERROR.ToString(), "Bot Execution Timeout", executionTimeoutInMinutes); + await backupRecordPersistanceService.UpdateStatusFeedAsync(id, BackupRecordStatus.ERROR.ToString(), "Bot Execution Timeout", executionTimeoutInMinutes); botsToRemove.Add(id.ToString()); } @@ -167,7 +160,7 @@ private void SetupBackgroundNonResponsiveStopService(CancellationToken cancellat if (deliveryRecordIds != null && deliveryRecordIds.Count > 0) foreach (string id in deliveryRecordIds) { - await contentDeliveryRecordPersistanceService.UpdateStatusFeedAsync(id, BackupRecordBackupStatus.ERROR.ToString(), "Bot Execution Timeout", executionTimeoutInMinutes); + await contentDeliveryRecordPersistanceService.UpdateStatusFeedAsync(id, BackupRecordStatus.ERROR.ToString(), "Bot Execution Timeout", executionTimeoutInMinutes); botsToRemove.Add(id); } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs index f256cfe..3287baa 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs @@ -1,7 +1,6 @@ using ICSharpCode.SharpZipLib.Zip; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using SemanticBackup.Core.Interfaces; using SemanticBackup.Core.Models; using System; using System.Diagnostics; @@ -17,44 +16,44 @@ internal class BackupZippingRobot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroupId; - - public string BotId => _backupRecord.Id.ToString(); - public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroupId}::{_backupRecord.Id}::{nameof(BackupZippingRobot)}"; + public string ResourceGroupId => _resourceGroupId; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; public BackupZippingRobot(string resourceGroupId, BackupRecord backupRecord, IServiceScopeFactory scopeFactory) { - this._resourceGroupId = resourceGroupId; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; - //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + _resourceGroupId = resourceGroupId; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"Creating Zip of Db: {_backupRecord.Path}"); - CheckIfFileExistsOrRemove(_backupRecord.Path); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("creating zip of: {Path}", _backupRecord.Path); + if (!File.Exists(_backupRecord.Path)) + throw new Exception($"No Database File In Path or may have been deleted, Path: {_backupRecord.Path}"); + //proceed + await Task.Delay(Random.Shared.Next(1000), cancellationToken); stopwatch.Start(); - + Status = BotStatus.Running; + //proceed string newZIPPath = _backupRecord.Path.Replace(".bak", ".zip"); - using (ZipOutputStream s = new ZipOutputStream(File.Create(newZIPPath))) + using (ZipOutputStream s = new(File.Create(newZIPPath))) { s.SetLevel(9); // 0 - store only to 9 - means best compression byte[] buffer = new byte[4096]; - var entry = new ZipEntry(Path.GetFileName(_backupRecord.Path)); - entry.DateTime = DateTime.UtcNow; + ZipEntry entry = new(Path.GetFileName(_backupRecord.Path)) + { + DateTime = DateTime.UtcNow + }; s.PutNextEntry(entry); using (FileStream fs = File.OpenRead(_backupRecord.Path)) { @@ -70,40 +69,33 @@ public async Task RunAsync() } stopwatch.Stop(); TryDeleteOldFile(_backupRecord.Path); - UpdateBackupFeed(_backupRecord.Id, BackupRecordBackupStatus.READY.ToString(), "Successfull & Ready", stopwatch.ElapsedMilliseconds, newZIPPath); - _logger.LogInformation($"Creating Zip of Db: {_backupRecord.Path}... SUCCESS"); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + BotId = BotId, + BackupRecordDeliveryId = _backupRecord.Id, + Status = BackupRecordStatus.READY, + Message = "Successfull & Ready", + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds, + NewFilePath = newZIPPath, + }, cancellationToken); + _logger.LogInformation("successfully zipped file: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; } catch (Exception ex) { + Status = BotStatus.Error; this._logger.LogError(ex.Message); stopwatch.Stop(); - UpdateBackupFeed(_backupRecord.Id, BackupRecordBackupStatus.ERROR.ToString(), ex.Message, stopwatch.ElapsedMilliseconds); - } - } - - private void CheckIfFileExistsOrRemove(string path) - { - if (!File.Exists(path)) - throw new Exception($"No Database File In Path or May have been deleted, Path: {path}"); - } - - private void UpdateBackupFeed(long recordId, string status, string message, long elapsed, string newZIPPath = null) - { - try - { - using (var scope = _scopeFactory.CreateScope()) + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed { - IBackupRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed, newZIPPath); - } - } - catch (Exception ex) - { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; + BotId = BotId, + BackupRecordDeliveryId = _backupRecord.Id.ToString(), + Status = BackupRecordStatus.ERROR, + Message = ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds, + }, cancellationToken); } } @@ -132,9 +124,11 @@ private void TryDeleteOldFile(string path) } } while (!success); - } - catch (Exception ex) { this._logger.LogWarning($"The File Name Failed to Delete,Error: {ex.Message}, File: {path}"); } + catch (Exception ex) + { + this._logger.LogWarning("The File Name Failed to Delete, Error: {Message}, File: {Path}", ex.Message, path); + } } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs index 38d8014..d722d44 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots @@ -16,60 +17,59 @@ internal class InDepthDeleteDropboxBot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _contentDeliveryRecord.Id; public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(InDepthDeleteDropboxBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; + public InDepthDeleteDropboxBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord, IServiceScopeFactory scopeFactory) { - this._contentDeliveryRecord = contentDeliveryRecord; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; + _contentDeliveryRecord = contentDeliveryRecord; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"DELETING Backup File From DropBox...."); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("Deleting backup file from DropBox: {Path}, Id: {Id}", this._backupRecord.Path, _contentDeliveryRecord.Id); + //proceed + await Task.Delay(Random.Shared.Next(1000), cancellationToken); DropboxDeliveryConfig settings = _resourceGroup.BackupDeliveryConfig.Dropbox ?? throw new Exception("no valid dropbox config"); stopwatch.Start(); + Status = BotStatus.Running; //Directory - string validDirectory = (string.IsNullOrWhiteSpace(settings.Directory)) ? "/" : settings.Directory; - validDirectory = (validDirectory.EndsWith("/")) ? validDirectory : validDirectory + "/"; - validDirectory = (validDirectory.StartsWith("/")) ? validDirectory : "/" + validDirectory; + string validDirectory = string.IsNullOrWhiteSpace(settings.Directory) ? "/" : settings.Directory; + validDirectory = validDirectory.EndsWith('/') ? validDirectory : validDirectory + "/"; + validDirectory = validDirectory.StartsWith('/') ? validDirectory : "/" + validDirectory; //Filename string fileName = Path.GetFileName(this._backupRecord.Path); //Proceed if (string.IsNullOrWhiteSpace(settings.AccessToken)) throw new Exception("Access Token is NULL"); //Proceed - using (DropboxClient dbx = new DropboxClient(settings.AccessToken.Trim())) + using (DropboxClient dbx = new(settings.AccessToken.Trim())) { string initialFileName = string.Format("{0}{1}", validDirectory, fileName); Dropbox.Api.Files.DeleteResult delResponse = await dbx.Files.DeleteV2Async(initialFileName, null); } stopwatch.Stop(); - _logger.LogInformation($"DELETING Backup File From DropBox: {_backupRecord.Path}... SUCCESS"); + _logger.LogInformation("Successfully deleted Backup File From DropBox: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; } catch (Exception ex) { + Status = BotStatus.Error; this._logger.LogWarning(ex.Message); stopwatch.Stop(); } - finally - { - this.IsCompleted = true; - } } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/MySQLBackupBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/MySQLBackupBot.cs index 2310a45..7247999 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/MySQLBackupBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/MySQLBackupBot.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots @@ -16,79 +17,72 @@ internal class MySQLBackupBot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _backupRecord.Id.ToString(); public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(MySQLBackupBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; + public MySQLBackupBot(string databaseName, ResourceGroup resourceGroup, BackupRecord backupRecord, IServiceScopeFactory serviceScopeFactory) { - this._databaseName = databaseName; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = serviceScopeFactory; + _databaseName = databaseName; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = serviceScopeFactory; //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"Creating Backup of Db: {_databaseName}"); - EnsureFolderExists(_backupRecord.Path); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("creating backup of Db: {_databaseName}", _databaseName); + string directory = Path.GetDirectoryName(_backupRecord.Path); + if (!Directory.Exists(directory)) + Directory.CreateDirectory(directory); + //proceed + await Task.Delay(Random.Shared.Next(1000), cancellationToken); stopwatch.Start(); + Status = BotStatus.Running; //Execute Service bool backupedUp = false; - using (var scope = _scopeFactory.CreateScope()) + using (IServiceScope scope = _scopeFactory.CreateScope()) { IBackupProviderForMySQLServer providerService = scope.ServiceProvider.GetRequiredService(); backupedUp = await providerService.BackupDatabaseAsync(_databaseName, _resourceGroup, _backupRecord); } stopwatch.Stop(); - if (backupedUp) - UpdateBackupFeed(_backupRecord.Id, BackupRecordBackupStatus.COMPLETED.ToString(), "Successfull", stopwatch.ElapsedMilliseconds); - else + if (!backupedUp) throw new Exception("Creating Backup Failed to Return Success Completion"); - _logger.LogInformation($"Creating Backup of Db: {_databaseName}...SUCCESS"); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + BotId = BotId, + BackupRecordDeliveryId = _backupRecord.Id.ToString(), + Status = BackupRecordStatus.COMPLETED, + Message = "Successfull", + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + + _logger.LogInformation("Successfully Backup of Db: {_databaseName}", _databaseName); + Status = BotStatus.Completed; } catch (Exception ex) { - this._logger.LogError(ex.Message); + Status = BotStatus.Error; + _logger.LogError(ex.Message); stopwatch.Stop(); - UpdateBackupFeed(_backupRecord.Id, BackupRecordBackupStatus.ERROR.ToString(), ex.Message, stopwatch.ElapsedMilliseconds); - } - } - - private void EnsureFolderExists(string path) - { - string directory = Path.GetDirectoryName(path); - if (!Directory.Exists(directory)) - Directory.CreateDirectory(directory); - } - - private void UpdateBackupFeed(long recordId, string status, string message, long elapsed) - { - try - { - using (var scope = _scopeFactory.CreateScope()) + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed { - IBackupRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed); - } - } - catch (Exception ex) - { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; + BotId = BotId, + BackupRecordDeliveryId = _backupRecord.Id.ToString(), + Status = BackupRecordStatus.ERROR, + Message = ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLBackupBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLBackupBot.cs index be73de6..4143672 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLBackupBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLBackupBot.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots @@ -16,79 +17,70 @@ internal class SQLBackupBot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _backupRecord.Id.ToString(); public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(SQLBackupBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; + public SQLBackupBot(string databaseName, ResourceGroup resourceGroup, BackupRecord backupRecord, IServiceScopeFactory scopeFactory) { - this._databaseName = databaseName; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; + _databaseName = databaseName; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"Creating Backup of Db: {_databaseName}"); - EnsureFolderExists(_backupRecord.Path); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("creating backup of Db: {_databaseName}", _databaseName); + string directory = Path.GetDirectoryName(_backupRecord.Path); + if (!Directory.Exists(directory)) + Directory.CreateDirectory(directory); + //proceed + await Task.Delay(Random.Shared.Next(1000), cancellationToken); stopwatch.Start(); + Status = BotStatus.Running; //Execute Service bool backupedUp = false; - using (var scope = _scopeFactory.CreateScope()) + using (IServiceScope scope = _scopeFactory.CreateScope()) { IBackupProviderForSQLServer backupProviderService = scope.ServiceProvider.GetRequiredService(); backupedUp = await backupProviderService.BackupDatabaseAsync(_databaseName, _resourceGroup, _backupRecord); } stopwatch.Stop(); - if (backupedUp) - UpdateBackupFeed(_backupRecord.Id, BackupRecordBackupStatus.COMPLETED.ToString(), "Successfull", stopwatch.ElapsedMilliseconds); - else + if (!backupedUp) throw new Exception("Creating Backup Failed to Return Success Completion"); - _logger.LogInformation($"Creating Backup of Db: {_databaseName}...SUCCESS"); - } - catch (Exception ex) - { - this._logger.LogError(ex.Message); - stopwatch.Stop(); - UpdateBackupFeed(_backupRecord.Id, BackupRecordBackupStatus.ERROR.ToString(), ex.Message, stopwatch.ElapsedMilliseconds); - } - } - - private void EnsureFolderExists(string path) - { - string directory = Path.GetDirectoryName(path); - if (!Directory.Exists(directory)) - Directory.CreateDirectory(directory); - } - - private void UpdateBackupFeed(long recordId, string status, string message, long elapsed) - { - try - { - using (var scope = _scopeFactory.CreateScope()) + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed { - IBackupRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed); - } + BotId = BotId, + BackupRecordDeliveryId = _backupRecord.Id.ToString(), + Status = BackupRecordStatus.COMPLETED, + Message = "Successfull", + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + _logger.LogInformation("Successfully Backup of Db: {_databaseName}", _databaseName); + Status = BotStatus.Completed; } catch (Exception ex) { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; + Status = BotStatus.Error; + _logger.LogError(ex.Message); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + BotId = BotId, + BackupRecordDeliveryId = _backupRecord.Id.ToString(), + Status = BackupRecordStatus.ERROR, + Message = ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLRestoreBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLRestoreBot.cs deleted file mode 100644 index 33e9b88..0000000 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLRestoreBot.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using SemanticBackup.Core.Interfaces; -using SemanticBackup.Core.Models; -using System; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; - -namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots -{ - internal class SQLRestoreBot : IBot - { - private readonly ResourceGroup _resourceGroup; - private readonly string _databaseName; - private readonly BackupRecord _backupRecord; - private readonly IServiceScopeFactory _scopeFactory; - private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _backupRecord.Id.ToString(); - public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; - public SQLRestoreBot(string databaseName, ResourceGroup resourceGroup, BackupRecord backupRecord, IServiceScopeFactory scopeFactory) - { - this._databaseName = databaseName; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; - //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); - } - public async Task RunAsync() - { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); - try - { - _logger.LogInformation($"Begining RESTORE of Db: {_databaseName}"); - EnsureBackupFileExists(_backupRecord.Path); - await Task.Delay(new Random().Next(1000)); - stopwatch.Start(); - //Execute Service - bool restoredSuccess = false; - using (var scope = _scopeFactory.CreateScope()) - { - IBackupProviderForSQLServer backupProviderService = scope.ServiceProvider.GetRequiredService(); - restoredSuccess = await backupProviderService.RestoreDatabaseAsync(_databaseName, _resourceGroup, _backupRecord); - } - stopwatch.Stop(); - if (restoredSuccess) - UpdateRestoreStatusFeed(_backupRecord.Id, BackupRecordRestoreStatus.RESTORE_COMPLETED.ToString(), "Restored Successfully"); - else - throw new Exception("RESTORE Failed to Return Success Completion"); - _logger.LogInformation($"RESTORE of Db: {_databaseName}...SUCCESS, Completion Time: {stopwatch.ElapsedMilliseconds:N0} Milliseconds"); - } - catch (Exception ex) - { - this._logger.LogError(ex.Message); - stopwatch.Stop(); - UpdateRestoreStatusFeed(_backupRecord.Id, BackupRecordRestoreStatus.FAILED_RESTORE.ToString(), ex.Message); - } - } - - private void EnsureBackupFileExists(string path) - { - if (!File.Exists(path)) - throw new Exception($"No Database File In Path or May have been deleted, Path: {path}"); - } - - private void UpdateRestoreStatusFeed(long recordId, string status, string message) - { - try - { - using (var scope = _scopeFactory.CreateScope()) - { - IBackupRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateRestoreStatusFeedAsync(recordId, status, message); - } - } - catch (Exception ex) - { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; - } - } - } -} diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs index 598f80e..8b5b605 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs @@ -1,11 +1,11 @@ using Azure.Storage.Blobs; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using SemanticBackup.Core.Interfaces; using SemanticBackup.Core.Models; using System; using System.Diagnostics; using System.IO; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots @@ -17,87 +17,79 @@ internal class UploaderAzureStorageBot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _contentDeliveryRecord.Id; public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(UploaderAzureStorageBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; + public UploaderAzureStorageBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord, IServiceScopeFactory scopeFactory) { - this._contentDeliveryRecord = contentDeliveryRecord; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; + _contentDeliveryRecord = contentDeliveryRecord; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"Uploading Backup File via Azure Blob Storage...."); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("uploading file to AzureBlobStorage: {Path}", _backupRecord.Path); + //proceed + await Task.Delay(Random.Shared.Next(1000), cancellationToken); AzureBlobStorageDeliveryConfig settings = _resourceGroup.BackupDeliveryConfig.AzureBlobStorage ?? throw new Exception("no valid azure blob storage config"); stopwatch.Start(); - //Upload FTP - CheckIfFileExistsOrRemove(this._backupRecord.Path); - //FTP Upload + Status = BotStatus.Running; + //check path + if (!File.Exists(_backupRecord.Path)) + throw new Exception($"No Database File In Path or May have been deleted, Path: {_backupRecord.Path}"); + //proceed string executionMessage = "Azure Blob Storage Uploading..."; //Container - string validContainer = (string.IsNullOrWhiteSpace(settings.BlobContainer)) ? "backups" : settings.BlobContainer; + string validContainer = string.IsNullOrWhiteSpace(settings.BlobContainer) ? "backups" : settings.BlobContainer; //Filename string fileName = Path.GetFileName(this._backupRecord.Path); //Proceed if (string.IsNullOrWhiteSpace(settings.ConnectionString)) throw new Exception("Invalid Connection String"); //Proceed - using (FileStream stream = File.Open(this._backupRecord.Path, FileMode.Open)) + using (FileStream stream = File.Open(_backupRecord.Path, FileMode.Open)) { - BlobContainerClient containerClient = new BlobContainerClient(settings.ConnectionString, validContainer); + BlobContainerClient containerClient = new(settings.ConnectionString, validContainer); BlobClient blobClient = containerClient.GetBlobClient(fileName); - _ = await blobClient.UploadAsync(stream, true); + _ = await blobClient.UploadAsync(stream, true, cancellationToken); executionMessage = $"Uploaded Container: {validContainer}"; } stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordDeliveryStatus.READY.ToString(), executionMessage, stopwatch.ElapsedMilliseconds); - _logger.LogInformation($"Uploading Backup File via Azure Blob Storage: {_backupRecord.Path}... SUCCESS"); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + BotId = BotId, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.READY, + Message = executionMessage, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + _logger.LogInformation("Successfully uploaded file to AzureBlobStorage: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; } catch (Exception ex) { - this._logger.LogError(ex.Message); + Status = BotStatus.Error; + _logger.LogError(ex.Message); stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordBackupStatus.ERROR.ToString(), (ex.InnerException != null) ? $"Error Uploading: {ex.InnerException.Message}" : ex.Message, stopwatch.ElapsedMilliseconds); - } - } - - private void CheckIfFileExistsOrRemove(string path) - { - if (!File.Exists(path)) - throw new Exception($"No Database File In Path or May have been deleted, Path: {path}"); - } - - private void UpdateBackupFeed(string recordId, string status, string message, long elapsed) - { - try - { - using (var scope = _scopeFactory.CreateScope()) + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed { - IContentDeliveryRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed); - } - } - catch (Exception ex) - { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; + BotId = BotId, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.ERROR, + Message = (ex.InnerException != null) ? $"Error: {ex.InnerException.Message}" : ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderDropboxBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderDropboxBot.cs index 2f4c5e9..a8bf851 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderDropboxBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderDropboxBot.cs @@ -7,6 +7,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots @@ -18,88 +19,94 @@ internal class UploaderDropboxBot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _contentDeliveryRecord.Id; public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(UploaderDropboxBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; + public UploaderDropboxBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord, IServiceScopeFactory scopeFactory) { - this._contentDeliveryRecord = contentDeliveryRecord; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; + _contentDeliveryRecord = contentDeliveryRecord; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"Uploading Backup File via DropBox...."); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("uploading file to Dropbox: {Path}", _backupRecord.Path); + await Task.Delay(Random.Shared.Next(1000), cancellationToken); DropboxDeliveryConfig settings = _resourceGroup.BackupDeliveryConfig.Dropbox ?? throw new Exception("no valid dropbox config"); stopwatch.Start(); - //Upload FTP - CheckIfFileExistsOrRemove(this._backupRecord.Path); + Status = BotStatus.Running; + //check path + if (!File.Exists(_backupRecord.Path)) + throw new Exception($"No Database File In Path or May have been deleted, Path: {_backupRecord.Path}"); //FTP Upload string executionMessage = "Dropbox Uploading..."; //Directory - string validDirectory = (string.IsNullOrWhiteSpace(settings.Directory)) ? "/" : settings.Directory; - validDirectory = (validDirectory.EndsWith("/")) ? validDirectory : validDirectory + "/"; - validDirectory = (validDirectory.StartsWith("/")) ? validDirectory : "/" + validDirectory; + string validDirectory = string.IsNullOrWhiteSpace(settings.Directory) ? "/" : settings.Directory; + validDirectory = validDirectory.EndsWith('/') ? validDirectory : validDirectory + "/"; + validDirectory = validDirectory.StartsWith('/') ? validDirectory : "/" + validDirectory; //Filename string fileName = Path.GetFileName(this._backupRecord.Path); //Proceed if (string.IsNullOrWhiteSpace(settings.AccessToken)) throw new Exception("Access Token is NULL"); //Proceed - using (DropboxClient dbx = new DropboxClient(settings.AccessToken.Trim())) - using (var mem = new MemoryStream(await File.ReadAllBytesAsync(this._backupRecord.Path))) + using (DropboxClient dbx = new(settings.AccessToken.Trim())) + using (MemoryStream mem = new(await File.ReadAllBytesAsync(this._backupRecord.Path, cancellationToken))) { - var updated = await dbx.Files.UploadAsync(string.Format("{0}{1}", validDirectory, fileName), WriteMode.Overwrite.Instance, body: mem); + FileMetadata updated = await dbx.Files.UploadAsync(string.Format("{0}{1}", validDirectory, fileName), WriteMode.Overwrite.Instance, body: mem); executionMessage = $"Uploaded to: {validDirectory}"; } stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordDeliveryStatus.READY.ToString(), executionMessage, stopwatch.ElapsedMilliseconds); - _logger.LogInformation($"Uploading Backup File via DropBox: {_backupRecord.Path}... SUCCESS"); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + BotId = BotId, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.READY, + Message = executionMessage, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + _logger.LogInformation("Successfully uploaded file to Dropbox: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; } catch (Exception ex) { - this._logger.LogError(ex.Message); + Status = BotStatus.Error; + _logger.LogError(ex.Message); stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordBackupStatus.ERROR.ToString(), (ex.InnerException != null) ? $"Error Uploading: {ex.InnerException.Message}" : ex.Message, stopwatch.ElapsedMilliseconds); + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + BotId = BotId, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.ERROR, + Message = (ex.InnerException != null) ? $"Error: {ex.InnerException.Message}" : ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); } } - private void CheckIfFileExistsOrRemove(string path) - { - if (!File.Exists(path)) - throw new Exception($"No Database File In Path or May have been deleted, Path: {path}"); - } - private void UpdateBackupFeed(string recordId, string status, string message, long elapsed) { try { - using (var scope = _scopeFactory.CreateScope()) - { - IContentDeliveryRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed); - } + using IServiceScope scope = _scopeFactory.CreateScope(); + IContentDeliveryRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); + _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed); } catch (Exception ex) { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; + _logger.LogError("Error Updating Feed: {Message}", ex.Message); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderEmailSMTPBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderEmailSMTPBot.cs index 98a003f..0ac7282 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderEmailSMTPBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderEmailSMTPBot.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SemanticBackup.Core; -using SemanticBackup.Core.Interfaces; using SemanticBackup.Core.Models; using System; using System.Collections.Generic; @@ -9,6 +8,7 @@ using System.IO; using System.Net.Mail; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots @@ -20,36 +20,36 @@ internal class UploaderEmailSMTPBot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _contentDeliveryRecord.Id; public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(UploaderEmailSMTPBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; public UploaderEmailSMTPBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord, IServiceScopeFactory scopeFactory) { - this._contentDeliveryRecord = contentDeliveryRecord; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; + _contentDeliveryRecord = contentDeliveryRecord; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"Sending Email via SMTP...."); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("uploading file to SMTP Mail: {Path}", _backupRecord.Path); + await Task.Delay(Random.Shared.Next(1000), cancellationToken); SmtpDeliveryConfig settings = _resourceGroup.BackupDeliveryConfig.Smtp ?? throw new Exception("no valid smtp config"); stopwatch.Start(); - //Upload FTP - CheckIfFileExistsOrRemove(this._backupRecord.Path); + Status = BotStatus.Running; + //check folder + if (!File.Exists(_backupRecord.Path)) + throw new Exception($"No Database File In Path or May have been deleted, Path: {_backupRecord.Path}"); //Check any Recepient List emailRecipients = settings.GetValidSmtpDestinations(); if (emailRecipients.Count == 0) @@ -57,86 +57,74 @@ public async Task RunAsync() //proceed string fileName = Path.GetFileName(this._backupRecord.Path); string executionMessage = "Sending Email...."; - using (MailMessage e_mail = new MailMessage()) + using (MailMessage e_mail = new()) { - using (SmtpClient Smtp_Server = new SmtpClient()) - { - //Configs - Smtp_Server.UseDefaultCredentials = false; - Smtp_Server.Credentials = new System.Net.NetworkCredential(settings.SMTPEmailAddress, settings.SMTPEmailCredentials); - Smtp_Server.Port = settings.SMTPPort; //Use 587 - Smtp_Server.EnableSsl = settings.SMTPEnableSSL; - Smtp_Server.Host = settings.SMTPHost; - //Attachment - byte[] fileContents; - using (StreamReader sourceStream = new StreamReader(this._backupRecord.Path)) - fileContents = Encoding.UTF8.GetBytes(await sourceStream.ReadToEndAsync()); - MemoryStream strm = new MemoryStream(fileContents); - Attachment AttachData = new Attachment(strm, fileName); - e_mail.Attachments.Add(AttachData); - //Other Configs - e_mail.From = new MailAddress(settings.SMTPEmailAddress, settings.SMTPDefaultSMTPFromName); - //This Configs Should be Placed here - e_mail.Subject = $"DATABASE BACKUP | {fileName}"; - e_mail.IsBodyHtml = true; - e_mail.Body = $"Find Attached Database Backup Record:
File: {fileName}



Powered By Crudsoft Technologies
email: support@crudsofttechnologies.com
"; + using SmtpClient Smtp_Server = new(); + //Configs + Smtp_Server.UseDefaultCredentials = false; + Smtp_Server.Credentials = new System.Net.NetworkCredential(settings.SMTPEmailAddress, settings.SMTPEmailCredentials); + Smtp_Server.Port = settings.SMTPPort; + Smtp_Server.EnableSsl = settings.SMTPEnableSSL; + Smtp_Server.Host = settings.SMTPHost; + //Attachment + byte[] fileContents; + using (StreamReader sourceStream = new(this._backupRecord.Path)) + fileContents = Encoding.UTF8.GetBytes(await sourceStream.ReadToEndAsync(cancellationToken)); + MemoryStream strm = new(fileContents); + Attachment AttachData = new(strm, fileName); + e_mail.Attachments.Add(AttachData); + //Other Configs + e_mail.From = new MailAddress(settings.SMTPEmailAddress, settings.SMTPDefaultSMTPFromName); + //This Configs Should be Placed here + e_mail.Subject = $"DATABASE BACKUP | {fileName}"; + e_mail.IsBodyHtml = true; + e_mail.Body = $"Find Attached Database Backup Record:
File: {fileName}



Powered By Crudsoft Technologies
email: support@crudsofttechnologies.com
"; - //Add Default - e_mail.To.Add(emailRecipients[0]); + //Add Default + e_mail.To.Add(emailRecipients[0]); - if (emailRecipients.Count > 1) + if (emailRecipients.Count > 1) + { + int addIndex = 0; + foreach (string dest in emailRecipients) { - int addIndex = 0; - foreach (string dest in emailRecipients) - { - //Skip the First - if (addIndex != 0) - e_mail.Bcc.Add(dest); - addIndex++; - } + //Skip the First + if (addIndex != 0) + e_mail.Bcc.Add(dest); + addIndex++; } - - //Finally Send - await Smtp_Server.SendMailAsync(e_mail); - executionMessage = $"Sent to: {settings.SMTPDestinations}"; } + + //Finally Send + await Smtp_Server.SendMailAsync(e_mail, cancellationToken); + executionMessage = $"Sent to: {settings.SMTPDestinations}"; } stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordDeliveryStatus.READY.ToString(), executionMessage, stopwatch.ElapsedMilliseconds); - _logger.LogInformation($"Sending Email via SMTP: {_backupRecord.Path}... SUCCESS"); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + BotId = BotId, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.READY, + Message = executionMessage, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + _logger.LogInformation("Successfully uploaded file to SMTP Server: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; } catch (Exception ex) { - this._logger.LogError(ex.Message); + Status = BotStatus.Error; + _logger.LogError(ex.Message); stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordBackupStatus.ERROR.ToString(), (ex.InnerException != null) ? $"Error Uploading: {ex.InnerException.Message}" : ex.Message, stopwatch.ElapsedMilliseconds); - } - } - - private void CheckIfFileExistsOrRemove(string path) - { - if (!File.Exists(path)) - throw new Exception($"No Database File In Path or May have been deleted, Path: {path}"); - } - - private void UpdateBackupFeed(string recordId, string status, string message, long elapsed) - { - try - { - - using (var scope = _scopeFactory.CreateScope()) + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed { - IContentDeliveryRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed); - } - } - catch (Exception ex) - { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; + BotId = BotId, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.ERROR, + Message = (ex.InnerException != null) ? $"Error: {ex.InnerException.Message}" : ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderFTPBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderFTPBot.cs index 2d343b5..512acab 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderFTPBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderFTPBot.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using SemanticBackup.Core.Interfaces; using SemanticBackup.Core.Models; using System; using System.Diagnostics; @@ -8,6 +7,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots @@ -19,42 +19,42 @@ internal class UploaderFTPBot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _contentDeliveryRecord.Id; public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(UploaderFTPBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; public UploaderFTPBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord, IServiceScopeFactory scopeFactory) { - this._contentDeliveryRecord = contentDeliveryRecord; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; + _contentDeliveryRecord = contentDeliveryRecord; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"Uploading Backup File via FTP...."); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("uploading file to FTP Server: {Path}", _backupRecord.Path); + await Task.Delay(Random.Shared.Next(1000), cancellationToken); FtpDeliveryConfig settings = _resourceGroup.BackupDeliveryConfig.Ftp ?? throw new Exception("no valid ftp config"); stopwatch.Start(); - //Upload FTP - CheckIfFileExistsOrRemove(this._backupRecord.Path); + Status = BotStatus.Running; + //check file + if (!File.Exists(_backupRecord.Path)) + throw new Exception($"No Database File In Path or May have been deleted, Path: {_backupRecord.Path}"); //FTP Upload string executionMessage = "FTP Uploading..."; //Directory string validDirectory = (string.IsNullOrWhiteSpace(settings.Directory)) ? "/" : settings.Directory; - validDirectory = (validDirectory.EndsWith("/")) ? validDirectory : validDirectory + "/"; - validDirectory = (validDirectory.StartsWith("/")) ? validDirectory : "/" + validDirectory; + validDirectory = validDirectory.EndsWith('/') ? validDirectory : validDirectory + "/"; + validDirectory = validDirectory.StartsWith('/') ? validDirectory : "/" + validDirectory; string validServerName = settings.Server.Replace("ftp", string.Empty).Replace("/", string.Empty).Replace(":", string.Empty); //Filename string fileName = Path.GetFileName(this._backupRecord.Path); @@ -66,21 +66,19 @@ public async Task RunAsync() using (FileStream sourceStream = File.OpenRead(this._backupRecord.Path)) { fileContents = new byte[sourceStream.Length]; - await sourceStream.ReadAsync(fileContents, 0, fileContents.Length); + await sourceStream.ReadAsync(fileContents, cancellationToken); + } + using HttpClient client = new(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{settings.Username}:{settings.Password}"))); + ByteArrayContent content = new(fileContents); + HttpResponseMessage response = await client.PutAsync(fullServerUrl, content, cancellationToken); + if (response.IsSuccessStatusCode) + { + executionMessage = $"Uploaded to Server: {settings.Server}"; } - using (HttpClient client = new HttpClient()) + else { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{settings.Username}:{settings.Password}"))); - ByteArrayContent content = new ByteArrayContent(fileContents); - HttpResponseMessage response = await client.PutAsync(fullServerUrl, content); - if (response.IsSuccessStatusCode) - { - executionMessage = $"Uploaded to Server: {settings.Server}"; - } - else - { - throw new Exception($"Failed to upload. Status code: {response.StatusCode}"); - } + throw new Exception($"Failed to upload. Status code: {response.StatusCode}"); } } catch (Exception ex) @@ -88,40 +86,32 @@ public async Task RunAsync() throw new Exception(ex.Message); } stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordDeliveryStatus.READY.ToString(), executionMessage, stopwatch.ElapsedMilliseconds); - _logger.LogInformation($"Uploading Backup File via FTP: {_backupRecord.Path}... SUCCESS"); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + BotId = BotId, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.READY, + Message = executionMessage, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + + _logger.LogInformation("Successfully uploaded file to FTP Server: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; } catch (Exception ex) { - this._logger.LogError(ex.Message); + Status = BotStatus.Error; + _logger.LogError(ex.Message); stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordBackupStatus.ERROR.ToString(), (ex.InnerException != null) ? $"Error Uploading: {ex.InnerException.Message}" : ex.Message, stopwatch.ElapsedMilliseconds); - } - } - - private void CheckIfFileExistsOrRemove(string path) - { - if (!File.Exists(path)) - throw new Exception($"No Database File In Path or May have been deleted, Path: {path}"); - } - - private void UpdateBackupFeed(string recordId, string status, string message, long elapsed) - { - try - { - using (var scope = _scopeFactory.CreateScope()) + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed { - IContentDeliveryRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed); - } - } - catch (Exception ex) - { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; + BotId = BotId, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.ERROR, + Message = (ex.InnerException != null) ? $"Error: {ex.InnerException.Message}" : ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderLinkGenBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderLinkGenBot.cs index 408b7e0..b37d983 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderLinkGenBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderLinkGenBot.cs @@ -2,10 +2,10 @@ using Microsoft.Extensions.Logging; using SemanticBackup.Core; using SemanticBackup.Core.Extensions; -using SemanticBackup.Core.Interfaces; using SemanticBackup.Core.Models; using System; using System.Diagnostics; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots @@ -17,67 +17,62 @@ internal class UploaderLinkGenBot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _contentDeliveryRecord.Id; public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(UploaderLinkGenBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; + public UploaderLinkGenBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord, IServiceScopeFactory scopeFactory) { - this._contentDeliveryRecord = contentDeliveryRecord; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; + _contentDeliveryRecord = contentDeliveryRecord; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"Creating Download Link...."); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("creating download link for file: {Path}", _backupRecord.Path); + await Task.Delay(Random.Shared.Next(1000), cancellationToken); DownloadLinkDeliveryConfig settings = _resourceGroup.BackupDeliveryConfig.DownloadLink ?? throw new Exception("no valid download link config"); stopwatch.Start(); + //get download link:: string contentLink = 5.GenerateUniqueId(); if (settings.DownloadLinkType == "LONG") contentLink = string.Format("{0}?token={1}", 55.GenerateUniqueId(), $"{this._backupRecord.Id}|{this._resourceGroup.Id}".ToMD5String()); - //Job to Do - await Task.Delay(new Random().Next(3000)); stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordDeliveryStatus.READY.ToString(), contentLink, stopwatch.ElapsedMilliseconds); - _logger.LogInformation($"Creating Download Link: {_backupRecord.Path}... SUCCESS"); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + BotId = BotId, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.READY, + Message = contentLink, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + _logger.LogInformation("Successfully created download link for file: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; } catch (Exception ex) { - this._logger.LogError(ex.Message); + Status = BotStatus.Error; + _logger.LogError(ex.Message); stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordBackupStatus.ERROR.ToString(), ex.Message, stopwatch.ElapsedMilliseconds); - } - } - - private void UpdateBackupFeed(string recordId, string status, string message, long elapsed) - { - try - { - using (var scope = _scopeFactory.CreateScope()) + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed { - IContentDeliveryRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed); - } - } - catch (Exception ex) - { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; + BotId = BotId, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.ERROR, + Message = (ex.InnerException != null) ? $"Error: {ex.InnerException.Message}" : ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/_IBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/_IBot.cs index 8fd53a3..630c891 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/_IBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/_IBot.cs @@ -1,15 +1,36 @@ -using System; +using SemanticBackup.Core.Models; +using System; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots { public interface IBot { - Task RunAsync(); - bool IsCompleted { get; } - bool IsStarted { get; } - string ResourceGroupId { get; } string BotId { get; } + string ResourceGroupId { get; } DateTime DateCreatedUtc { get; } + Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken); + BotStatus Status { get; } + } + + public class BackupRecordDeliveryFeed + { + public string BotId { get; set; } + public string BackupRecordDeliveryId { get; set; } + public BackupRecordStatus Status { get; set; } + public string Message { get; set; } + public string NewFilePath { get; set; } = null; + public long ElapsedMilliseconds { get; set; } = 0; } + + public enum BotStatus + { + NotReady, + Starting, + Running, + Completed, + Error + } + } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs index d309351..4d2f490 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs @@ -12,7 +12,7 @@ namespace SemanticBackup.Infrastructure.BackgroundJobs public class BotsManagerBackgroundJob : IHostedService { private readonly ILogger _logger; - private List Bots { get; set; } = new List(); + private List Bots { get; set; } = []; public BotsManagerBackgroundJob(ILogger logger) { @@ -21,7 +21,7 @@ public BotsManagerBackgroundJob(ILogger logger) public Task StartAsync(CancellationToken cancellationToken) { - Bots = new List(); + Bots = []; SetupBotsBackgroundService(cancellationToken); return Task.CompletedTask; } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/RestoreBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/RestoreBackgroundJob.cs deleted file mode 100644 index 9fc6962..0000000 --- a/SemanticBackup.Infrastructure/BackgroundJobs/RestoreBackgroundJob.cs +++ /dev/null @@ -1,101 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using SemanticBackup.Core.Interfaces; -using SemanticBackup.Core.Models; -using SemanticBackup.Infrastructure.BackgroundJobs.Bots; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace SemanticBackup.Infrastructure.BackgroundJobs -{ - public class RestoreBackgroundJob : IHostedService - { - private readonly ILogger _logger; - private readonly IServiceScopeFactory _serviceScopeFactory; - private readonly BotsManagerBackgroundJob _botsManagerBackgroundJob; - - public RestoreBackgroundJob( - ILogger logger, - IServiceScopeFactory serviceScopeFactory, - BotsManagerBackgroundJob botsManagerBackgroundJob) - { - this._logger = logger; - this._serviceScopeFactory = serviceScopeFactory; - this._botsManagerBackgroundJob = botsManagerBackgroundJob; - } - - public Task StartAsync(CancellationToken cancellationToken) - { - _logger.LogInformation("Starting service...."); - SetupBackgroundService(cancellationToken); - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - private void SetupBackgroundService(CancellationToken cancellationToken) - { - var t = new Thread(async () => - { - while (!cancellationToken.IsCancellationRequested) - { - await Task.Delay(5000); - try - { - using (var scope = _serviceScopeFactory.CreateScope()) - { - //DI INJECTIONS - IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); - IDatabaseInfoRepository databaseInfoPersistanceService = scope.ServiceProvider.GetRequiredService(); - //Proceed - List backupRestores = await backupRecordPersistanceService.GetAllByRestoreStatusAsync(BackupRecordRestoreStatus.PENDING_RESTORE.ToString()); - if (backupRestores != null && backupRestores.Count > 0) - { - foreach (BackupRecord backupRecord in backupRestores.OrderBy(x => x.RegisteredDateUTC)) - { - _logger.LogInformation($"Processing Queued Backup RESTORE Record Key: #{backupRecord.Id}..."); - BackupDatabaseInfo backupDatabaseInfo = await databaseInfoPersistanceService.GetByIdAsync(backupRecord.BackupDatabaseInfoId); - ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupDatabaseInfo.ResourceGroupId); - if (backupDatabaseInfo != null && resourceGroup != null) - { - if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, resourceGroup.MaximumRunningBots)) - { - if (resourceGroup.DbType.Contains("SQLSERVER")) - _botsManagerBackgroundJob.AddBot(new SQLRestoreBot(backupDatabaseInfo.DatabaseName, resourceGroup, backupRecord, _serviceScopeFactory)); - else if (resourceGroup.DbType.Contains("MYSQL") || resourceGroup.DbType.Contains("MARIADB")) - throw new Exception("No RESTORE Bot for MYSQL"); - else - throw new Exception($"No Bot is registered to Handle Database RESTORE of Type: {resourceGroup.DbType}"); - //Finally Update Status - bool updated = await backupRecordPersistanceService.UpdateRestoreStatusFeedAsync(backupRecord.Id, BackupRecordRestoreStatus.EXECUTING_RESTORE.ToString(), "Executing Restore...."); - if (updated) - _logger.LogInformation($"Processing Queued Backup RESTORE Record Key: #{backupRecord.Id}...SUCCESS"); - else - _logger.LogWarning($"Queued for Backup RESTORE but was unable to update backup record Key: #{backupRecord.Id} status"); - } - else - _logger.LogInformation($"Resource Group With Id: {resourceGroup.Id} has Exceeded its Maximum Allocated Running Threads Count: {resourceGroup.MaximumRunningBots}"); - } - } - } - } - } - catch (Exception ex) - { - _logger.LogError(ex.Message); - } - - } - }); - t.Start(); - } - } -} diff --git a/SemanticBackup.Infrastructure/Implementations/BackupRecordRepositoryLiteDb.cs b/SemanticBackup.Infrastructure/Implementations/BackupRecordRepositoryLiteDb.cs index d2bead5..3d44ae8 100644 --- a/SemanticBackup.Infrastructure/Implementations/BackupRecordRepositoryLiteDb.cs +++ b/SemanticBackup.Infrastructure/Implementations/BackupRecordRepositoryLiteDb.cs @@ -193,7 +193,7 @@ public async Task UpdateAsync(BackupRecord record) public async Task> GetAllReadyAndPendingDeliveryAsync() { - return await _db.GetCollection().Query().Where(x => !x.ExecutedDeliveryRun && x.BackupStatus == BackupRecordBackupStatus.READY.ToString()).OrderBy(x => x.Id).ToListAsync(); + return await _db.GetCollection().Query().Where(x => !x.ExecutedDeliveryRun && x.BackupStatus == BackupRecordStatus.READY.ToString()).OrderBy(x => x.Id).ToListAsync(); } public async Task UpdateDeliveryRunnedAsync(long backupRecordId, bool hasRun, string executedDeliveryRunStatus) { diff --git a/SemanticBackup.Infrastructure/Implementations/ResourceGroupRepositoryLiteDb.cs b/SemanticBackup.Infrastructure/Implementations/ResourceGroupRepositoryLiteDb.cs index 1751f55..40c5c70 100644 --- a/SemanticBackup.Infrastructure/Implementations/ResourceGroupRepositoryLiteDb.cs +++ b/SemanticBackup.Infrastructure/Implementations/ResourceGroupRepositoryLiteDb.cs @@ -10,7 +10,7 @@ namespace SemanticBackup.Infrastructure.Implementations { public class ResourceGroupRepositoryLiteDb : IResourceGroupRepository { - private LiteDatabaseAsync _db; + private readonly LiteDatabaseAsync _db; private readonly IBackupRecordRepository _backupRecordPersistanceService; private readonly IBackupScheduleRepository _backupSchedulePersistanceService; private readonly IDatabaseInfoRepository _databaseInfoPersistanceService; diff --git a/SemanticBackup/Pages/ResourceGroups/DatabaseBackups/Details.cshtml.cs b/SemanticBackup/Pages/ResourceGroups/DatabaseBackups/Details.cshtml.cs index 03754a7..8fd8ee8 100644 --- a/SemanticBackup/Pages/ResourceGroups/DatabaseBackups/Details.cshtml.cs +++ b/SemanticBackup/Pages/ResourceGroups/DatabaseBackups/Details.cshtml.cs @@ -100,11 +100,11 @@ private async Task InitiateRerunForBackupAsync() { //re-run here //:: ensure backup record exists - if (BackupRecordResponse.BackupStatus != BackupRecordBackupStatus.ERROR.ToString()) + if (BackupRecordResponse.BackupStatus != BackupRecordStatus.ERROR.ToString()) throw new Exception($"STATUS need to be ERROR, Current Status for this record is: {BackupRecordResponse.BackupStatus}"); //prepare re-run string newBackupPath = BackupRecordResponse.Path.Replace(".zip", ".bak"); - bool rerunSuccess = await _backupRecordRepository.UpdateStatusFeedAsync(BackupRecordResponse.Id, BackupRecordBackupStatus.QUEUED.ToString(), "Queued for Re-run", 0, newBackupPath); + bool rerunSuccess = await _backupRecordRepository.UpdateStatusFeedAsync(BackupRecordResponse.Id, BackupRecordStatus.QUEUED.ToString(), "Queued for Re-run", 0, newBackupPath); //update status this.RerunStatus = "success"; this.RerunStatusReason = "Success"; @@ -125,7 +125,7 @@ private async Task InitiateRerunForDeliveryAsync(string rerunJobId) if (contentDeliveryRecord == null) throw new Exception($"No delivery content with specified job: {rerunJobId}"); //check status - else if (contentDeliveryRecord.CurrentStatus != BackupRecordBackupStatus.ERROR.ToString()) + else if (contentDeliveryRecord.CurrentStatus != BackupRecordStatus.ERROR.ToString()) throw new Exception($"STATUS needs to be ERROR state, Current Status for this record is: {contentDeliveryRecord.CurrentStatus}"); //prepare re-run bool rerunSuccess = await _contentDeliveryRecordRepository.UpdateStatusFeedAsync(contentDeliveryRecord.Id, BackupRecordDeliveryStatus.QUEUED.ToString(), "Queued for Re-run", 0); diff --git a/SemanticBackup/Pages/ResourceGroups/Databases/Details.cshtml.cs b/SemanticBackup/Pages/ResourceGroups/Databases/Details.cshtml.cs index 4215a02..13a4df4 100644 --- a/SemanticBackup/Pages/ResourceGroups/Databases/Details.cshtml.cs +++ b/SemanticBackup/Pages/ResourceGroups/Databases/Details.cshtml.cs @@ -83,7 +83,7 @@ public async Task OnGetAsync(string resourceGroupId, string id) if (DatabaseInfoResponse == null) return null; //Check if an Existing Queued - List queuedExisting = await this._backupRecordRepository.GetAllByDatabaseIdByStatusAsync(DatabaseInfoResponse.Id, BackupRecordBackupStatus.QUEUED.ToString()); + List queuedExisting = await this._backupRecordRepository.GetAllByDatabaseIdByStatusAsync(DatabaseInfoResponse.Id, BackupRecordStatus.QUEUED.ToString()); if (queuedExisting != null && queuedExisting.Count > 0) return queuedExisting.FirstOrDefault()?.Id; //init requeue db @@ -93,7 +93,7 @@ public async Task OnGetAsync(string resourceGroupId, string id) BackupRecord newRecord = new BackupRecord { BackupDatabaseInfoId = DatabaseInfoResponse.Id, - BackupStatus = BackupRecordBackupStatus.QUEUED.ToString(), + BackupStatus = BackupRecordStatus.QUEUED.ToString(), ExpiryDateUTC = RecordExpiryUTC, Name = $"{DatabaseInfoResponse.DatabaseName} on {CurrentResourceGroup.DbServer}", Path = Path.Combine(_options.DefaultBackupDirectory, CurrentResourceGroup.GetSavingPathFromFormat(DatabaseInfoResponse.DatabaseName, _options.BackupFileSaveFormat, currentTimeUTC)), diff --git a/SemanticBackup/Services/StatusNotificationService.cs b/SemanticBackup/Services/StatusNotificationService.cs index e7dede7..0b8a9ff 100644 --- a/SemanticBackup/Services/StatusNotificationService.cs +++ b/SemanticBackup/Services/StatusNotificationService.cs @@ -40,7 +40,7 @@ private async Task SendBackupRecordEmailNotificationAsync(BackupRecord backupRec try { _logger.LogInformation("Received BackupRecordUpdated Notification...."); - if (backupRecord.BackupStatus != BackupRecordBackupStatus.ERROR.ToString()) + if (backupRecord.BackupStatus != BackupRecordStatus.ERROR.ToString()) return; using (var scope = _scopeFactory.CreateScope()) { @@ -72,7 +72,7 @@ private async Task SendContentDeliveryNotificationAsync(BackupRecordDelivery rec try { _logger.LogInformation("Received BackupRecordUpdated Notification...."); - if (record.CurrentStatus != BackupRecordBackupStatus.ERROR.ToString()) + if (record.CurrentStatus != BackupRecordStatus.ERROR.ToString()) return; using (var scope = _scopeFactory.CreateScope()) { diff --git a/SemanticBackup/SignalRHubs/DashboardRefreshHubDispatcher.cs b/SemanticBackup/SignalRHubs/DashboardRefreshHubDispatcher.cs index e66f1b6..4e5f44f 100644 --- a/SemanticBackup/SignalRHubs/DashboardRefreshHubDispatcher.cs +++ b/SemanticBackup/SignalRHubs/DashboardRefreshHubDispatcher.cs @@ -154,7 +154,7 @@ private async Task SendMetricsAsync(string groupRecord) } } - var recordsFailsLatest = await backupRecordPersistanceService.GetAllByStatusUpdateDateByStatusAsync(resourcegroup, metricsFromDatUTC, BackupRecordBackupStatus.ERROR.ToString()); + var recordsFailsLatest = await backupRecordPersistanceService.GetAllByStatusUpdateDateByStatusAsync(resourcegroup, metricsFromDatUTC, BackupRecordStatus.ERROR.ToString()); if (recordsFailsLatest != null && recordsFailsLatest.Count > 0) foreach (var record in recordsFailsLatest) { From 796cc53c450c64fdf1277a6a933ca0a567b9692f Mon Sep 17 00:00:00 2001 From: "George Njeri (Swagfin)" Date: Sun, 13 Apr 2025 16:07:58 +0300 Subject: [PATCH 4/7] updated bots manager --- .../BotsManagerBackgroundJob.cs | 35 +++++++++++++++---- .../ContentDeliveryRecordRepositoryLiteDb.cs | 6 ++-- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs index 4d2f490..d774113 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs @@ -1,5 +1,7 @@ -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using SemanticBackup.Core.Interfaces; using SemanticBackup.Infrastructure.BackgroundJobs.Bots; using System; using System.Collections.Generic; @@ -12,11 +14,14 @@ namespace SemanticBackup.Infrastructure.BackgroundJobs public class BotsManagerBackgroundJob : IHostedService { private readonly ILogger _logger; + private readonly IServiceScopeFactory _serviceScopeFactory; + private List Bots { get; set; } = []; - public BotsManagerBackgroundJob(ILogger logger) + public BotsManagerBackgroundJob(ILogger logger, IServiceScopeFactory serviceScopeFactory) { - this._logger = logger; + _logger = logger; + _serviceScopeFactory = serviceScopeFactory; } public Task StartAsync(CancellationToken cancellationToken) @@ -42,6 +47,7 @@ public void TerminateBots(List botIds) this.Bots.Remove(bot); } } + public bool HasAvailableResourceGroupBotsCount(string resourceGroupId, int maximumThreads = 1) { int resourceBots = this.Bots.Where(x => x.ResourceGroupId == resourceGroupId).Count(); @@ -60,10 +66,11 @@ private void SetupBotsBackgroundService(CancellationToken cancellationToken) if (this.Bots != null && this.Bots.Count > 0) { //Start and Stop Bacup Bots - List botsNotStarted = this.Bots.Where(x => !x.IsStarted).OrderBy(x => x.DateCreatedUtc).ToList(); - if (botsNotStarted != null && botsNotStarted.Count > 0) - foreach (IBot bot in botsNotStarted) - _ = bot.RunAsync(); + List botsNotReady = (Bots.Where(x => x.Status == BotStatus.NotReady).OrderBy(x => x.DateCreatedUtc).ToList()) ?? []; + foreach (IBot bot in botsNotReady) + { + _ = bot.RunAsync(OnDeliveryFeedUpdate, cancellationToken); + } //Remove Completed List botsCompleted = this.Bots.Where(x => x.IsCompleted).ToList(); if (botsCompleted != null && botsCompleted.Count > 0) @@ -78,5 +85,19 @@ private void SetupBotsBackgroundService(CancellationToken cancellationToken) }); t.Start(); } + + private async Task OnDeliveryFeedUpdate(BackupRecordDeliveryFeed feed, CancellationToken token) + { + try + { + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IContentDeliveryRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); + await _persistanceService.UpdateStatusFeedAsync(feed.BackupRecordDeliveryId, feed.Status.ToString(), feed.Message, feed.ElapsedMilliseconds); + } + catch (Exception ex) + { + _logger.LogError("Error Updating Status Feed: {Message}", ex.Message); + } + } } } diff --git a/SemanticBackup.Infrastructure/Implementations/ContentDeliveryRecordRepositoryLiteDb.cs b/SemanticBackup.Infrastructure/Implementations/ContentDeliveryRecordRepositoryLiteDb.cs index c7f28cf..fcd05f3 100644 --- a/SemanticBackup.Infrastructure/Implementations/ContentDeliveryRecordRepositoryLiteDb.cs +++ b/SemanticBackup.Infrastructure/Implementations/ContentDeliveryRecordRepositoryLiteDb.cs @@ -58,8 +58,8 @@ public async Task AddOrUpdateAsync(BackupRecordDelivery record) public async Task UpdateStatusFeedAsync(string id, string status, string message = null, long executionInMilliseconds = 0) { - var collection = _db.GetCollection(); - var objFound = await collection.Query().Where(x => x.Id == id).FirstOrDefaultAsync(); + ILiteCollectionAsync collection = _db.GetCollection(); + BackupRecordDelivery objFound = await collection.Query().Where(x => x.Id == id).FirstOrDefaultAsync(); if (objFound != null) { objFound.CurrentStatus = status; @@ -98,7 +98,7 @@ public async Task> GetAllNoneResponsiveAsync(List statusChe private void DispatchUpdatedStatus(BackupRecordDelivery record, bool isNewRecord = false) { if (_backupRecordStatusChangedNotifiers != null) - foreach (var notifier in _backupRecordStatusChangedNotifiers) + foreach (IRecordStatusChangedNotifier notifier in _backupRecordStatusChangedNotifiers) try { notifier.DispatchContentDeliveryUpdatedStatus(record, isNewRecord); From d80312ca0d44e05ab47327b192d15ead637251b1 Mon Sep 17 00:00:00 2001 From: "George Njeri (Swagfin)" Date: Sun, 13 Apr 2025 16:28:47 +0300 Subject: [PATCH 5/7] chore: major refactoring --- .../BackgroundJobs/Bots/BackupZippingBot.cs | 10 ++-- .../BackgroundJobs/Bots/MySQLBackupBot.cs | 10 ++-- .../BackgroundJobs/Bots/SQLBackupBot.cs | 10 ++-- .../Bots/UploaderAzureStorageBot.cs | 6 ++- .../BackgroundJobs/Bots/UploaderDropboxBot.cs | 6 ++- .../Bots/UploaderEmailSMTPBot.cs | 6 ++- .../BackgroundJobs/Bots/UploaderFTPBot.cs | 6 ++- .../BackgroundJobs/Bots/UploaderLinkGenBot.cs | 6 ++- .../BackgroundJobs/Bots/_IBot.cs | 10 +++- .../BotsManagerBackgroundJob.cs | 47 ++++++++++++------- 10 files changed, 76 insertions(+), 41 deletions(-) diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs index 3287baa..06da2a3 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs @@ -72,8 +72,9 @@ public async Task RunAsync(Func logger, IServi public Task StartAsync(CancellationToken cancellationToken) { - Bots = []; SetupBotsBackgroundService(cancellationToken); return Task.CompletedTask; } @@ -40,11 +39,11 @@ public Task StopAsync(CancellationToken cancellationToken) public void AddBot(List bots) => Bots.AddRange(bots); public void TerminateBots(List botIds) { - if (botIds != null && botIds.Count > 0) + if (botIds.Count != 0) { - List botsToRemove = this.Bots.Where(x => botIds.Contains(x.BotId)).ToList(); + List botsToRemove = [.. Bots.Where(x => botIds.Contains(x.BotId))]; foreach (IBot bot in botsToRemove) - this.Bots.Remove(bot); + Bots.Remove(bot); } } @@ -55,32 +54,35 @@ public bool HasAvailableResourceGroupBotsCount(string resourceGroupId, int maxim int availableResourceGrpThreads = maximumThreads - runningResourceGrpThreads; return availableResourceGrpThreads > 0; } + private void SetupBotsBackgroundService(CancellationToken cancellationToken) { - var t = new Thread(async () => + Thread t = new(async () => { while (!cancellationToken.IsCancellationRequested) { try { - if (this.Bots != null && this.Bots.Count > 0) + if (Bots.Count > 0) { - //Start and Stop Bacup Bots + //Start not ready bots List botsNotReady = (Bots.Where(x => x.Status == BotStatus.NotReady).OrderBy(x => x.DateCreatedUtc).ToList()) ?? []; foreach (IBot bot in botsNotReady) { _ = bot.RunAsync(OnDeliveryFeedUpdate, cancellationToken); } - //Remove Completed - List botsCompleted = this.Bots.Where(x => x.IsCompleted).ToList(); - if (botsCompleted != null && botsCompleted.Count > 0) - foreach (IBot bot in botsCompleted) - this.Bots.Remove(bot); + //Remove Completed or Error + List botsCompleted = (Bots.Where(x => x.Status == BotStatus.Completed || x.Status == BotStatus.Error).ToList()) ?? []; + foreach (IBot bot in botsCompleted) + this.Bots.Remove(bot); } } - catch (Exception ex) { _logger.LogWarning($"Running Unstarted and Removing Completed Bots Failed: {ex.Message}"); } + catch (Exception ex) + { + _logger.LogWarning("Error: {Message}", ex.Message); + } //Delay - await Task.Delay(5000); + await Task.Delay(3000, cancellationToken); } }); t.Start(); @@ -90,9 +92,20 @@ private async Task OnDeliveryFeedUpdate(BackupRecordDeliveryFeed feed, Cancellat { try { - using IServiceScope scope = _serviceScopeFactory.CreateScope(); - IContentDeliveryRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - await _persistanceService.UpdateStatusFeedAsync(feed.BackupRecordDeliveryId, feed.Status.ToString(), feed.Message, feed.ElapsedMilliseconds); + if (feed.DeliveryFeedType == DeliveryFeedType.BackupNotify && feed.BackupRecordId > 0) + { + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IBackupRecordRepository _backupRecordRepo = scope.ServiceProvider.GetRequiredService(); + await _backupRecordRepo.UpdateStatusFeedAsync(feed.BackupRecordId, feed.Status.ToString(), feed.Message, feed.ElapsedMilliseconds, feed.NewFilePath); + } + else if (feed.DeliveryFeedType == DeliveryFeedType.BackupDeliveryNotify && !string.IsNullOrWhiteSpace(feed.BackupRecordDeliveryId)) + { + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IContentDeliveryRecordRepository _contentDeliveryRepo = scope.ServiceProvider.GetRequiredService(); + await _contentDeliveryRepo.UpdateStatusFeedAsync(feed.BackupRecordDeliveryId, feed.Status.ToString(), feed.Message, feed.ElapsedMilliseconds); + } + else + throw new Exception($"unsupported delivery-feed-type: {feed.DeliveryFeedType}"); } catch (Exception ex) { From e366cbc699b51d6cbb2329f776668e0b0798ef80 Mon Sep 17 00:00:00 2001 From: "George Njeri (Swagfin)" Date: Mon, 14 Apr 2025 01:49:58 +0300 Subject: [PATCH 6/7] 5.1.4 --- .../Models/Requests/RSSettings.cs | 12 ++ .../Models/Requests/ResourceGroupRequest.cs | 1 + SemanticBackup.Core/Models/ResourceGroup.cs | 20 ++- .../BackgroundJobs/BackupBackgroundJob.cs | 98 +++++++------- ...ckupRecordDeliveryDispatchBackgroundJob.cs | 7 +- ...kupRecordDeliverySchedulerBackgroundJob.cs | 110 ++++++++------- .../Bots/InDepthDeleteObjectStorageBot.cs | 72 ++++++++++ .../Bots/UploaderAzureStorageBot.cs | 2 +- .../Bots/UploaderObjectStorageBot.cs | 100 ++++++++++++++ .../BotsManagerBackgroundJob.cs | 5 +- .../ContentDeliveryRecordRepositoryLiteDb.cs | 4 +- .../SemanticBackup.Infrastructure.csproj | 1 + .../Pages/ResourceGroups/Create.cshtml | 127 ++++++++++++------ .../Pages/ResourceGroups/Create.cshtml.cs | 22 ++- SemanticBackup/SemanticBackup.csproj | 4 +- 15 files changed, 425 insertions(+), 160 deletions(-) create mode 100644 SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteObjectStorageBot.cs create mode 100644 SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderObjectStorageBot.cs diff --git a/SemanticBackup.Core/Models/Requests/RSSettings.cs b/SemanticBackup.Core/Models/Requests/RSSettings.cs index cc5ab73..4e3e68f 100644 --- a/SemanticBackup.Core/Models/Requests/RSSettings.cs +++ b/SemanticBackup.Core/Models/Requests/RSSettings.cs @@ -48,10 +48,22 @@ public class RSDropBoxSetting public string AccessToken { get; set; } public string Directory { get; set; } = "/"; } + public class RSAzureBlobStorageSetting { public bool IsEnabled { get; set; } = false; public string ConnectionString { get; set; } public string BlobContainer { get; set; } } + + public class RSObjectStorageSetting + { + public bool IsEnabled { get; set; } = false; + public string Server { get; set; } = "localhost"; + public int Port { get; set; } = 9000; + public string Bucket { get; set; } = "public"; + public string AccessKey { get; set; } = string.Empty; + public string SecretKey { get; set; } = string.Empty; + public bool UseSsl { get; set; } = false; + } } diff --git a/SemanticBackup.Core/Models/Requests/ResourceGroupRequest.cs b/SemanticBackup.Core/Models/Requests/ResourceGroupRequest.cs index 2b15009..f62d923 100644 --- a/SemanticBackup.Core/Models/Requests/ResourceGroupRequest.cs +++ b/SemanticBackup.Core/Models/Requests/ResourceGroupRequest.cs @@ -26,6 +26,7 @@ public class ResourceGroupRequest public RSEmailSMTPSetting RSEmailSMTPSetting { get; set; } = null; public RSDropBoxSetting RSDropBoxSetting { get; set; } = null; public RSAzureBlobStorageSetting RSAzureBlobStorageSetting { get; set; } = null; + public RSObjectStorageSetting RSObjectStorageSetting { get; set; } = null; public bool NotifyOnErrorBackups { get; set; } = false; public bool NotifyOnErrorBackupDelivery { get; set; } = false; public string NotifyEmailDestinations { get; set; } = null; diff --git a/SemanticBackup.Core/Models/ResourceGroup.cs b/SemanticBackup.Core/Models/ResourceGroup.cs index 091584e..80146d7 100644 --- a/SemanticBackup.Core/Models/ResourceGroup.cs +++ b/SemanticBackup.Core/Models/ResourceGroup.cs @@ -30,6 +30,7 @@ public class ResourceGroup public bool NotifyOnErrorBackupDelivery { get; set; } = false; public string NotifyEmailDestinations { get; set; } = null; } + public class BackupDeliveryConfig { public DownloadLinkDeliveryConfig DownloadLink { get; set; } = new DownloadLinkDeliveryConfig(); @@ -37,12 +38,15 @@ public class BackupDeliveryConfig public SmtpDeliveryConfig Smtp { get; set; } = new SmtpDeliveryConfig(); public DropboxDeliveryConfig Dropbox { get; set; } = new DropboxDeliveryConfig(); public AzureBlobStorageDeliveryConfig AzureBlobStorage { get; set; } = new AzureBlobStorageDeliveryConfig(); + public ObjectStorageDeliveryConfig ObjectStorage { get; set; } = new ObjectStorageDeliveryConfig(); } + public class DownloadLinkDeliveryConfig { public bool IsEnabled { get; set; } = true; public string DownloadLinkType { get; set; } } + public class FtpDeliveryConfig { public bool IsEnabled { get; set; } = false; @@ -51,6 +55,7 @@ public class FtpDeliveryConfig public string Password { get; set; } public string Directory { get; set; } = "/"; } + public class SmtpDeliveryConfig { public bool IsEnabled { get; set; } = false; @@ -62,6 +67,7 @@ public class SmtpDeliveryConfig public string SMTPDefaultSMTPFromName { get; set; } public string SMTPDestinations { get; set; } } + public class DropboxDeliveryConfig { public bool IsEnabled { get; set; } = false; @@ -74,6 +80,18 @@ public class AzureBlobStorageDeliveryConfig public string ConnectionString { get; set; } public string BlobContainer { get; set; } } + + public class ObjectStorageDeliveryConfig + { + public bool IsEnabled { get; set; } = false; + public string Server { get; set; } = "localhost"; + public int Port { get; set; } = 9000; + public string Bucket { get; set; } = "public"; + public string AccessKey { get; set; } = string.Empty; + public string SecretKey { get; set; } = string.Empty; + public bool UseSsl { get; set; } = false; + } + //enums public enum DbTypes { @@ -81,6 +99,6 @@ public enum DbTypes } public enum BackupDeliveryConfigTypes { - DownloadLink, Ftp, Smtp, Dropbox, AzureBlobStorage + DownloadLink, Ftp, Smtp, Dropbox, AzureBlobStorage, ObjectStorage } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs index 5e0ffd9..016ae3d 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs @@ -22,10 +22,10 @@ public class BackupBackgroundJob : IHostedService public BackupBackgroundJob(ILogger logger, SystemConfigOptions persistanceOptions, IServiceScopeFactory serviceScopeFactory, BotsManagerBackgroundJob botsManagerBackgroundJob) { - this._logger = logger; - this._persistanceOptions = persistanceOptions; - this._serviceScopeFactory = serviceScopeFactory; - this._botsManagerBackgroundJob = botsManagerBackgroundJob; + _logger = logger; + _persistanceOptions = persistanceOptions; + _serviceScopeFactory = serviceScopeFactory; + _botsManagerBackgroundJob = botsManagerBackgroundJob; } public Task StartAsync(CancellationToken cancellationToken) @@ -51,64 +51,61 @@ private void SetupBackgroundService(CancellationToken cancellationToken) await Task.Delay(5000, cancellationToken); try { - using (var scope = _serviceScopeFactory.CreateScope()) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + //DI Injections + IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + IDatabaseInfoRepository databaseInfoPersistanceService = scope.ServiceProvider.GetRequiredService(); + IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); + //Proceed + List queuedBackups = await backupRecordPersistanceService.GetAllByStatusAsync(BackupRecordStatus.QUEUED.ToString()); + if (queuedBackups != null && queuedBackups.Count > 0) { - //DI Injections - IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - IDatabaseInfoRepository databaseInfoPersistanceService = scope.ServiceProvider.GetRequiredService(); - IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); - //Proceed - List queuedBackups = await backupRecordPersistanceService.GetAllByStatusAsync(BackupRecordStatus.QUEUED.ToString()); - if (queuedBackups != null && queuedBackups.Count > 0) + List scheduleToDelete = []; + foreach (BackupRecord backupRecord in queuedBackups.OrderBy(x => x.RegisteredDateUTC).ToList()) { - List scheduleToDelete = []; - foreach (BackupRecord backupRecord in queuedBackups.OrderBy(x => x.RegisteredDateUTC).ToList()) + _logger.LogInformation($"Processing Queued Backup Record Key: #{backupRecord.Id}..."); + BackupDatabaseInfo backupDatabaseInfo = await databaseInfoPersistanceService.GetByIdAsync(backupRecord.BackupDatabaseInfoId); + if (backupDatabaseInfo == null) { - _logger.LogInformation($"Processing Queued Backup Record Key: #{backupRecord.Id}..."); - BackupDatabaseInfo backupDatabaseInfo = await databaseInfoPersistanceService.GetByIdAsync(backupRecord.BackupDatabaseInfoId); - if (backupDatabaseInfo == null) + _logger.LogWarning($"No Database Info matches with Id: {backupRecord.BackupDatabaseInfoId}, Backup Database Record will be Deleted: {backupRecord.Id}"); + scheduleToDelete.Add(backupRecord.Id); + } + else + { + //Check if valid Resource Group + ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupDatabaseInfo.ResourceGroupId); + if (resourceGroup == null) { - _logger.LogWarning($"No Database Info matches with Id: {backupRecord.BackupDatabaseInfoId}, Backup Database Record will be Deleted: {backupRecord.Id}"); + _logger.LogWarning($"The Database Id: {backupRecord.BackupDatabaseInfoId}, doesn't seem to have been assigned to a valid Resource Group Id: {backupDatabaseInfo.ResourceGroupId}, Record will be Deleted"); scheduleToDelete.Add(backupRecord.Id); } else { - //Check if valid Resource Group - ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupDatabaseInfo.ResourceGroupId); - if (resourceGroup == null) + if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, resourceGroup.MaximumRunningBots)) { - _logger.LogWarning($"The Database Id: {backupRecord.BackupDatabaseInfoId}, doesn't seem to have been assigned to a valid Resource Group Id: {backupDatabaseInfo.ResourceGroupId}, Record will be Deleted"); - scheduleToDelete.Add(backupRecord.Id); - } - else - { - if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, resourceGroup.MaximumRunningBots)) - { - if (resourceGroup.DbType.Contains("SQLSERVER")) - _botsManagerBackgroundJob.AddBot(new SQLBackupBot(backupDatabaseInfo.DatabaseName, resourceGroup, backupRecord, _serviceScopeFactory)); - else if (resourceGroup.DbType.Contains("MYSQL") || resourceGroup.DbType.Contains("MARIADB")) - _botsManagerBackgroundJob.AddBot(new MySQLBackupBot(backupDatabaseInfo.DatabaseName, resourceGroup, backupRecord, _serviceScopeFactory)); - else - throw new Exception($"No Bot is registered to Handle Database Backups of Type: {resourceGroup.DbType}"); - //Finally Update Status - bool updated = await backupRecordPersistanceService.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordStatus.EXECUTING.ToString()); - if (updated) - _logger.LogInformation($"Processing Queued Backup Record Key: #{backupRecord.Id}...SUCCESS"); - else - _logger.LogWarning($"Queued for Backup but was unable to update backup record Key: #{backupRecord.Id} status"); - } + if (resourceGroup.DbType.Contains("SQLSERVER")) + _botsManagerBackgroundJob.AddBot(new SQLBackupBot(backupDatabaseInfo.DatabaseName, resourceGroup, backupRecord, _serviceScopeFactory)); + else if (resourceGroup.DbType.Contains("MYSQL") || resourceGroup.DbType.Contains("MARIADB")) + _botsManagerBackgroundJob.AddBot(new MySQLBackupBot(backupDatabaseInfo.DatabaseName, resourceGroup, backupRecord, _serviceScopeFactory)); + else + throw new Exception($"No Bot is registered to Handle Database Backups of Type: {resourceGroup.DbType}"); + //Finally Update Status + bool updated = await backupRecordPersistanceService.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordStatus.EXECUTING.ToString()); + if (updated) + _logger.LogInformation($"Processing Queued Backup Record Key: #{backupRecord.Id}...SUCCESS"); else - _logger.LogInformation($"Resource Group With Id: {resourceGroup.Id} has Exceeded its Maximum Allocated Running Threads Count: {resourceGroup.MaximumRunningBots}"); + _logger.LogWarning($"Queued for Backup but was unable to update backup record Key: #{backupRecord.Id} status"); } - + else + _logger.LogInformation($"Resource Group With Id: {resourceGroup.Id} has Exceeded its Maximum Allocated Running Threads Count: {resourceGroup.MaximumRunningBots}"); } + } - //Check if Any Delete - if (scheduleToDelete.Count > 0) - foreach (var rm in scheduleToDelete) - await backupRecordPersistanceService.RemoveAsync(rm); } - + //Check if Any Delete + if (scheduleToDelete.Count > 0) + foreach (var rm in scheduleToDelete) + await backupRecordPersistanceService.RemoveAsync(rm); } } @@ -190,6 +187,11 @@ private async Task StartInDepthDeleteForAsync(BackupRecord rm) //In Depth Remove From DropBox botsManagerBackgroundJob.AddBot(new InDepthDeleteDropboxBot(resourceGroup, rm, rec, _serviceScopeFactory)); } + else if (rec.DeliveryType == BackupDeliveryConfigTypes.ObjectStorage.ToString()) + { + //In Depth Remove From Object Storage + botsManagerBackgroundJob.AddBot(new InDepthDeleteObjectStorageBot(resourceGroup, rm, rec, _serviceScopeFactory)); + } } } catch (Exception ex) { _logger.LogError(ex.Message); } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs index ea03165..a62adbb 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs @@ -112,6 +112,11 @@ private void SetupBackgroundService(CancellationToken cancellationToken) //Azure Blob Storage _botsManagerBackgroundJob.AddBot(new UploaderAzureStorageBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.ObjectStorage.ToString()) + { + //Object Storage + _botsManagerBackgroundJob.AddBot(new UploaderObjectStorageBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); + } else { status = BackupRecordDeliveryStatus.ERROR.ToString(); @@ -160,7 +165,7 @@ private void SetupBackgroundRemovedExpiredBackupsService(CancellationToken cance List expiredBackups = await backupRecordPersistanceService.GetAllExpiredAsync(); if (expiredBackups != null && expiredBackups.Count > 0) { - List toDeleteList = new List(); + List toDeleteList = new(); foreach (BackupRecord backupRecord in expiredBackups) toDeleteList.Add(backupRecord.Id); _logger.LogInformation($"Queued ({expiredBackups.Count}) Expired Records for Delete"); diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliverySchedulerBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliverySchedulerBackgroundJob.cs index a043a28..5b11b34 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliverySchedulerBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliverySchedulerBackgroundJob.cs @@ -16,13 +16,12 @@ public class BackupRecordDeliverySchedulerBackgroundJob : IHostedService { private readonly ILogger _logger; private readonly IServiceScopeFactory _serviceScopeFactory; - public BackupRecordDeliverySchedulerBackgroundJob( - ILogger logger, - IServiceScopeFactory serviceScopeFactory) + public BackupRecordDeliverySchedulerBackgroundJob(ILogger logger, IServiceScopeFactory serviceScopeFactory) { this._logger = logger; this._serviceScopeFactory = serviceScopeFactory; } + public Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Starting service...."); @@ -44,66 +43,65 @@ private void SetupBackgroundService(CancellationToken cancellationToken) await Task.Delay(4000, cancellationToken); try { - using (var scope = _serviceScopeFactory.CreateScope()) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + //DI INJECTIONS + IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + //Proceed + List pendingExecutionRecords = await backupRecordPersistanceService.GetAllReadyAndPendingDeliveryAsync(); + foreach (BackupRecord backupRecord in pendingExecutionRecords?.OrderBy(x => x.RegisteredDateUTC)?.ToList()) { - //DI INJECTIONS - IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - //Proceed - List pendingExecutionRecords = await backupRecordPersistanceService.GetAllReadyAndPendingDeliveryAsync(); - foreach (BackupRecord backupRecord in pendingExecutionRecords?.OrderBy(x => x.RegisteredDateUTC)?.ToList()) - { - _logger.LogInformation($"Queueing Content Delivery for Backup Record Id: {backupRecord.Id}..."); - //## get other services - IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); - IContentDeliveryRecordRepository contentDeliveryRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - IDatabaseInfoRepository databaseInfoRepository = scope.ServiceProvider.GetRequiredService(); - //get db information - BackupDatabaseInfo backupRecordDbInfo = await databaseInfoRepository.GetByIdAsync(backupRecord.BackupDatabaseInfoId); - //Check if valid Resource Group - ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupRecordDbInfo?.ResourceGroupId ?? string.Empty); - //Has Valid Resource Group + _logger.LogInformation($"Queueing Content Delivery for Backup Record Id: {backupRecord.Id}..."); + //## get other services + IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); + IContentDeliveryRecordRepository contentDeliveryRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + IDatabaseInfoRepository databaseInfoRepository = scope.ServiceProvider.GetRequiredService(); + //get db information + BackupDatabaseInfo backupRecordDbInfo = await databaseInfoRepository.GetByIdAsync(backupRecord.BackupDatabaseInfoId); + //Check if valid Resource Group + ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupRecordDbInfo?.ResourceGroupId ?? string.Empty); + //Has Valid Resource Group - //check if backup delivery config is set - if (resourceGroup.BackupDeliveryConfig == null) - { - _logger.LogInformation($"Resource Group Id: {backupRecord.Id}, doesn't have any backup delivery config, Skipped"); - _ = await backupRecordPersistanceService.UpdateDeliveryRunnedAsync(backupRecord.Id, true, BackupRecordExecutedDeliveryRunStatus.SKIPPED_EXECUTION.ToString()); - } - else + //check if backup delivery config is set + if (resourceGroup.BackupDeliveryConfig == null) + { + _logger.LogInformation($"Resource Group Id: {backupRecord.Id}, doesn't have any backup delivery config, Skipped"); + _ = await backupRecordPersistanceService.UpdateDeliveryRunnedAsync(backupRecord.Id, true, BackupRecordExecutedDeliveryRunStatus.SKIPPED_EXECUTION.ToString()); + } + else + { + //loop delivery types + foreach (BackupDeliveryConfigTypes deliveryType in Enum.GetValues(typeof(BackupDeliveryConfigTypes))) { - //loop delivery types - foreach (BackupDeliveryConfigTypes deliveryType in Enum.GetValues(typeof(BackupDeliveryConfigTypes))) + bool isDeliveryEnabled = false; + switch (deliveryType) { - bool isDeliveryEnabled = false; - switch (deliveryType) - { - case BackupDeliveryConfigTypes.DownloadLink: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.DownloadLink?.IsEnabled ?? false; break; - case BackupDeliveryConfigTypes.Ftp: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.Ftp?.IsEnabled ?? false; break; - case BackupDeliveryConfigTypes.Smtp: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.Smtp?.IsEnabled ?? false; break; - case BackupDeliveryConfigTypes.Dropbox: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.Dropbox?.IsEnabled ?? false; break; - case BackupDeliveryConfigTypes.AzureBlobStorage: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.AzureBlobStorage?.IsEnabled ?? false; break; - default: isDeliveryEnabled = false; break; - } - //check if enabled - if (isDeliveryEnabled) + case BackupDeliveryConfigTypes.DownloadLink: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.DownloadLink?.IsEnabled ?? false; break; + case BackupDeliveryConfigTypes.Ftp: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.Ftp?.IsEnabled ?? false; break; + case BackupDeliveryConfigTypes.Smtp: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.Smtp?.IsEnabled ?? false; break; + case BackupDeliveryConfigTypes.Dropbox: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.Dropbox?.IsEnabled ?? false; break; + case BackupDeliveryConfigTypes.AzureBlobStorage: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.AzureBlobStorage?.IsEnabled ?? false; break; + case BackupDeliveryConfigTypes.ObjectStorage: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.ObjectStorage?.IsEnabled ?? false; break; + default: isDeliveryEnabled = false; break; + } + //check if enabled + if (isDeliveryEnabled) + { + bool queuedSuccess = await contentDeliveryRecordPersistanceService.AddOrUpdateAsync(new BackupRecordDelivery { - bool queuedSuccess = await contentDeliveryRecordPersistanceService.AddOrUpdateAsync(new BackupRecordDelivery - { - Id = $"{backupRecord.Id}|{resourceGroup.Id}|{deliveryType}".ToMD5String().ToUpper(), //Unique Identification - BackupRecordId = backupRecord.Id, - CurrentStatus = BackupRecordDeliveryStatus.QUEUED.ToString(), - DeliveryType = deliveryType.ToString(), - RegisteredDateUTC = DateTime.UtcNow, - StatusUpdateDateUTC = DateTime.UtcNow, - ExecutionMessage = "Queued for Dispatch" - }); - if (!queuedSuccess) - _logger.LogWarning($"unable to queue Backup Record Id: {backupRecord.Id} for delivery via : {deliveryType}, resource group: {resourceGroup.Name}"); - } + Id = $"{backupRecord.Id}|{resourceGroup.Id}|{deliveryType}".ToMD5String().ToUpper(), //Unique Identification + BackupRecordId = backupRecord.Id, + CurrentStatus = BackupRecordDeliveryStatus.QUEUED.ToString(), + DeliveryType = deliveryType.ToString(), + RegisteredDateUTC = DateTime.UtcNow, + StatusUpdateDateUTC = DateTime.UtcNow, + ExecutionMessage = "Queued for Dispatch" + }); + if (!queuedSuccess) + _logger.LogWarning($"unable to queue Backup Record Id: {backupRecord.Id} for delivery via : {deliveryType}, resource group: {resourceGroup.Name}"); } - //Update Execution - _ = await backupRecordPersistanceService.UpdateDeliveryRunnedAsync(backupRecord.Id, true, BackupRecordExecutedDeliveryRunStatus.SUCCESSFULLY_EXECUTED.ToString()); } + //Update Execution + _ = await backupRecordPersistanceService.UpdateDeliveryRunnedAsync(backupRecord.Id, true, BackupRecordExecutedDeliveryRunStatus.SUCCESSFULLY_EXECUTED.ToString()); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteObjectStorageBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteObjectStorageBot.cs new file mode 100644 index 0000000..6c03c31 --- /dev/null +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteObjectStorageBot.cs @@ -0,0 +1,72 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Minio; +using Minio.DataModel.Args; +using SemanticBackup.Core.Models; +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots +{ + internal class InDepthDeleteObjectStorageBot : IBot + { + private readonly BackupRecordDelivery _contentDeliveryRecord; + private readonly ResourceGroup _resourceGroup; + private readonly BackupRecord _backupRecord; + private readonly IServiceScopeFactory _scopeFactory; + private readonly ILogger _logger; + public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(InDepthDeleteObjectStorageBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; + + public InDepthDeleteObjectStorageBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord, IServiceScopeFactory scopeFactory) + { + _contentDeliveryRecord = contentDeliveryRecord; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; + //Logger + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); + } + + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) + { + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); + try + { + _logger.LogInformation("deleting uploaded file from ObjectStorage: {Path}, Id: {Id}", _backupRecord.Path, _contentDeliveryRecord.Id); + //proceed + await Task.Delay(Random.Shared.Next(1000), cancellationToken); + ObjectStorageDeliveryConfig settings = _resourceGroup.BackupDeliveryConfig.ObjectStorage ?? throw new Exception("no valid object storage config"); + stopwatch.Start(); + Status = BotStatus.Running; + //Container + string validBucket = string.IsNullOrWhiteSpace(settings.Bucket) ? "backups" : settings.Bucket; + //Filename + string fileName = Path.GetFileName(this._backupRecord.Path); + //Proceed + using IMinioClient minioClient = new MinioClient().WithEndpoint(settings.Server, settings.Port).WithCredentials(settings.AccessKey, settings.SecretKey).WithSSL(settings.UseSsl).Build(); + { + await minioClient.RemoveObjectAsync(new RemoveObjectArgs() + .WithBucket(validBucket) + .WithObject(fileName), cancellationToken); + } + stopwatch.Stop(); + _logger.LogInformation("Successfully deleted file from ObjectStorage: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; + } + catch (Exception ex) + { + Status = BotStatus.Error; + _logger.LogError(ex.Message); + stopwatch.Stop(); + } + } + } +} diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs index 111d11f..def18a3 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs @@ -62,7 +62,7 @@ public async Task RunAsync(Func _logger; + public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(UploaderObjectStorageBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; + + public UploaderObjectStorageBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord, IServiceScopeFactory scopeFactory) + { + _contentDeliveryRecord = contentDeliveryRecord; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; + //Logger + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); + } + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) + { + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); + try + { + _logger.LogInformation("uploading file to ObjectStorage: {Path}", _backupRecord.Path); + //proceed + await Task.Delay(Random.Shared.Next(1000), cancellationToken); + ObjectStorageDeliveryConfig settings = _resourceGroup.BackupDeliveryConfig.ObjectStorage ?? throw new Exception("no valid object storage config"); + stopwatch.Start(); + Status = BotStatus.Running; + //check path + if (!File.Exists(_backupRecord.Path)) + throw new Exception($"No Database File In Path or May have been deleted, Path: {_backupRecord.Path}"); + //proceed + string executionMessage = "Object Storage Uploading..."; + //Container + string validBucket = string.IsNullOrWhiteSpace(settings.Bucket) ? "backups" : settings.Bucket; + //Filename + string fileName = Path.GetFileName(this._backupRecord.Path); + //Proceed + using IMinioClient minioClient = new MinioClient().WithEndpoint(settings.Server, settings.Port).WithCredentials(settings.AccessKey, settings.SecretKey).WithSSL(settings.UseSsl).Build(); + using (FileStream stream = File.Open(_backupRecord.Path, FileMode.Open)) + { + //upload object + await minioClient.PutObjectAsync(new PutObjectArgs() + .WithBucket(settings.Bucket) + .WithObject(fileName) + .WithStreamData(stream) + .WithObjectSize(stream.Length), cancellationToken); + executionMessage = $"Uploaded to Bucket: {validBucket}"; + } + stopwatch.Stop(); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + DeliveryFeedType = DeliveryFeedType.BackupDeliveryNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.READY, + Message = executionMessage, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + _logger.LogInformation("Successfully uploaded file to ObjectStorage: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; + } + catch (Exception ex) + { + Status = BotStatus.Error; + _logger.LogError(ex.Message); + stopwatch.Stop(); + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + DeliveryFeedType = DeliveryFeedType.BackupDeliveryNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.ERROR, + Message = (ex.InnerException != null) ? $"Error: {ex.InnerException.Message}" : ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + } + } + } +} diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs index 8e19715..83176d9 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs @@ -49,10 +49,7 @@ public void TerminateBots(List botIds) public bool HasAvailableResourceGroupBotsCount(string resourceGroupId, int maximumThreads = 1) { - int resourceBots = this.Bots.Where(x => x.ResourceGroupId == resourceGroupId).Count(); - int runningResourceGrpThreads = resourceBots; - int availableResourceGrpThreads = maximumThreads - runningResourceGrpThreads; - return availableResourceGrpThreads > 0; + return Bots.Count(x => x.ResourceGroupId == resourceGroupId) < maximumThreads; } private void SetupBotsBackgroundService(CancellationToken cancellationToken) diff --git a/SemanticBackup.Infrastructure/Implementations/ContentDeliveryRecordRepositoryLiteDb.cs b/SemanticBackup.Infrastructure/Implementations/ContentDeliveryRecordRepositoryLiteDb.cs index fcd05f3..266cc6b 100644 --- a/SemanticBackup.Infrastructure/Implementations/ContentDeliveryRecordRepositoryLiteDb.cs +++ b/SemanticBackup.Infrastructure/Implementations/ContentDeliveryRecordRepositoryLiteDb.cs @@ -35,8 +35,8 @@ public async Task> GetAllByBackupRecordIdAsync(long i public async Task AddOrUpdateAsync(BackupRecordDelivery record) { - var collection = _db.GetCollection(); - var objFound = await collection.Query().Where(x => x.Id == record.Id).FirstOrDefaultAsync(); + ILiteCollectionAsync collection = _db.GetCollection(); + BackupRecordDelivery objFound = await collection.Query().Where(x => x.Id == record.Id).FirstOrDefaultAsync(); if (objFound != null) { objFound.StatusUpdateDateUTC = record.StatusUpdateDateUTC; diff --git a/SemanticBackup.Infrastructure/SemanticBackup.Infrastructure.csproj b/SemanticBackup.Infrastructure/SemanticBackup.Infrastructure.csproj index 5b9181b..bfa3372 100644 --- a/SemanticBackup.Infrastructure/SemanticBackup.Infrastructure.csproj +++ b/SemanticBackup.Infrastructure/SemanticBackup.Infrastructure.csproj @@ -18,6 +18,7 @@ +
diff --git a/SemanticBackup/Pages/ResourceGroups/Create.cshtml b/SemanticBackup/Pages/ResourceGroups/Create.cshtml index 31639fa..c7da344 100644 --- a/SemanticBackup/Pages/ResourceGroups/Create.cshtml +++ b/SemanticBackup/Pages/ResourceGroups/Create.cshtml @@ -51,42 +51,42 @@

Database Configuration

-
- -
-
- - -
-
- - -
-
- - -
-
- -
-
- - -
-
- - -
-
- -
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
@@ -111,11 +111,12 @@
@@ -217,7 +218,7 @@
- +
@@ -230,6 +231,46 @@
+ +
+ + + + + +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + + + + +
+
@@ -269,7 +310,7 @@ -@section Scripts{ +@section Scripts {