From f9b66766c2c36b04cf0af3c07973a7dafd0a235d Mon Sep 17 00:00:00 2001 From: "George Njeri (Swagfin)" Date: Sun, 29 Jun 2025 15:04:27 +0300 Subject: [PATCH 1/4] chore: optimization patches --- ...imeExtensions.cs => DateTimeExtensions.cs} | 26 +++- .../BackgroundJobs/BackupBackgroundJob.cs | 68 ++++----- .../BackgroundJobs/BackupBackgroundZIPJob.cs | 56 +++---- ...ckupRecordDeliveryDispatchBackgroundJob.cs | 144 +++++++++--------- ...kupRecordDeliverySchedulerBackgroundJob.cs | 14 +- .../BackupSchedulerBackgroundJob.cs | 79 +++++----- .../BackgroundJobs/Bots/BackupZippingBot.cs | 2 +- .../Bots/InDepthDeleteDropboxBot.cs | 2 +- .../Bots/InDepthDeleteObjectStorageBot.cs | 2 +- .../BackgroundJobs/Bots/MySQLBackupBot.cs | 1 - .../BackgroundJobs/Bots/SQLBackupBot.cs | 1 - .../Bots/UploaderAzureStorageBot.cs | 1 - .../BackgroundJobs/Bots/UploaderDropboxBot.cs | 2 +- .../Bots/UploaderEmailSMTPBot.cs | 1 - .../BackgroundJobs/Bots/UploaderFTPBot.cs | 1 - .../BackgroundJobs/Bots/UploaderLinkGenBot.cs | 1 - .../Bots/UploaderObjectStorageBot.cs | 1 - .../BotsManagerBackgroundJob.cs | 1 - .../DatabaseBackups/Details.cshtml | 12 +- 19 files changed, 202 insertions(+), 213 deletions(-) rename SemanticBackup.Core/Extensions/{TimeExtensions.cs => DateTimeExtensions.cs} (82%) diff --git a/SemanticBackup.Core/Extensions/TimeExtensions.cs b/SemanticBackup.Core/Extensions/DateTimeExtensions.cs similarity index 82% rename from SemanticBackup.Core/Extensions/TimeExtensions.cs rename to SemanticBackup.Core/Extensions/DateTimeExtensions.cs index ed3adf7..da149ae 100644 --- a/SemanticBackup.Core/Extensions/TimeExtensions.cs +++ b/SemanticBackup.Core/Extensions/DateTimeExtensions.cs @@ -1,9 +1,10 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; namespace SemanticBackup.Core { - public static class TimeExtensions + public static class DateTimeExtensions { public static DateTime ConvertFromUTC(this DateTime dateTime, string timezone) { @@ -22,22 +23,27 @@ public static DateTime ConvertToUTC(this DateTime dateTime, string timezone) dateTime = SetKind(dateTime); return TimeZoneInfo.ConvertTimeToUtc(dateTime, tz); } + private static DateTime SetKind(DateTime date) => DateTime.SpecifyKind(date, DateTimeKind.Unspecified); + public static DateTime IgnoreSeconds(this DateTime time, bool end) { time = new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, 0, 0, time.Kind); return end ? time.AddMinutes(1) : time; } + public static DateTime StartOfWeek(this DateTime dt, DayOfWeek startOfWeek = DayOfWeek.Monday) { int diff = (7 + (dt.DayOfWeek - startOfWeek)) % 7; return dt.AddDays(-1 * diff).Date; } + public static DateTime EndOfWeek(this DateTime dt, DayOfWeek startOfWeek = DayOfWeek.Sunday) { int diff = (7 + (startOfWeek - dt.DayOfWeek)) % 7; return dt.AddDays(diff).Date; } + public static DateTime AdjustWithTimezoneOffset(this DateTime dateTime, string timezoneOffset = "00:00") { try @@ -80,5 +86,23 @@ public static string ToTimezoneWithOffsetString(this TimeZoneInfo timeZoneInfo) (string timezone, string offset) timezoneWithOffset = ToTimezoneWithOffset(timeZoneInfo); return string.Format("{0} ({1})", timezoneWithOffset.timezone, timezoneWithOffset.offset); } + + public static string ToUtcDifferenceString(this DateTime targetUtc, DateTime? compareUtc = null) + { + DateTime baseTime = compareUtc ?? DateTime.UtcNow; + TimeSpan diff = targetUtc - baseTime; + if (diff.TotalSeconds <= 0) return "Expired"; + + int days = diff.Days; + int hours = diff.Hours; + int minutes = diff.Minutes; + + List parts = []; + if (days > 0) parts.Add($"{days} day(s)"); + if (hours > 0) parts.Add($"{hours}hr{(hours == 1 ? "" : "s")}"); + if (minutes > 0) parts.Add($"{minutes}min"); + + return parts.Count > 0 ? string.Join(" ", parts) : "Expired"; + } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs index 94c4b10..7be207a 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs @@ -6,6 +6,7 @@ using SemanticBackup.Infrastructure.BackgroundJobs.Bots; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -74,55 +75,48 @@ private void SetupBackgroundService(CancellationToken cancellationToken) try { //Proceed - List queuedBackups = await _backupRecordRepository.GetAllByStatusAsync(BackupRecordStatus.QUEUED.ToString()); - if (queuedBackups != null && queuedBackups.Count > 0) + List queuedBackups = (await _backupRecordRepository.GetAllByStatusAsync(BackupRecordStatus.QUEUED.ToString())) ?? []; + List scheduleToDelete = []; + foreach (BackupRecord backupRecord in queuedBackups.OrderBy(x => x.Id).ToList()) { - List scheduleToDelete = []; - foreach (BackupRecord backupRecord in queuedBackups.OrderBy(x => x.RegisteredDateUTC).ToList()) + _logger.LogInformation("Processing Queued Backup Record Key: #{Id}...", backupRecord.Id); + BackupDatabaseInfo backupDatabaseInfo = await _databaseInfoRepository.GetByIdAsync(backupRecord.BackupDatabaseInfoId); + if (backupDatabaseInfo == null) { - _logger.LogInformation($"Processing Queued Backup Record Key: #{backupRecord.Id}..."); - BackupDatabaseInfo backupDatabaseInfo = await _databaseInfoRepository.GetByIdAsync(backupRecord.BackupDatabaseInfoId); - if (backupDatabaseInfo == null) + _logger.LogWarning("No Database Info matches with Id: {BackupDatabaseInfoId}, Backup Database Record will be Deleted: {Id}", backupRecord.BackupDatabaseInfoId, backupRecord.Id); + scheduleToDelete.Add(backupRecord.Id); + } + else + { + //Check if valid Resource Group + ResourceGroup resourceGroup = await _resourceGroupRepository.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: {BackupDatabaseInfoId}, doesn't seem to have been assigned to a valid Resource Group Id: {ResourceGroupId}, Record will be Deleted", backupRecord.BackupDatabaseInfoId, backupDatabaseInfo.ResourceGroupId); scheduleToDelete.Add(backupRecord.Id); } else { - //Check if valid Resource Group - ResourceGroup resourceGroup = await _resourceGroupRepository.GetByIdOrKeyAsync(backupDatabaseInfo.ResourceGroupId); - if (resourceGroup == null) - { - _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 (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, resourceGroup.MaximumRunningBots)) - { - if (resourceGroup.DbType.Contains("SQLSERVER")) - _botsManagerBackgroundJob.AddBot(new SQLBackupBot(backupDatabaseInfo.DatabaseName, resourceGroup, backupRecord, _providerForSQLServer)); - else if (resourceGroup.DbType.Contains("MYSQL") || resourceGroup.DbType.Contains("MARIADB")) - _botsManagerBackgroundJob.AddBot(new MySQLBackupBot(backupDatabaseInfo.DatabaseName, resourceGroup, backupRecord, _providerForMySqlServer)); - else - throw new Exception($"No Bot is registered to Handle Database Backups of Type: {resourceGroup.DbType}"); - //Finally Update Status - bool updated = await _backupRecordRepository.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, _providerForSQLServer)); + else if (resourceGroup.DbType.Contains("MYSQL") || resourceGroup.DbType.Contains("MARIADB")) + _botsManagerBackgroundJob.AddBot(new MySQLBackupBot(backupDatabaseInfo.DatabaseName, resourceGroup, backupRecord, _providerForMySqlServer)); else - _logger.LogInformation($"Resource Group With Id: {resourceGroup.Id} Bots are Busy, Running Bots Count: {resourceGroup.MaximumRunningBots}, waiting for available Bots...."); + throw new Exception($"No Bot is registered to Handle Database Backups of Type: {resourceGroup.DbType}"); + //Finally Update Status + _ = await _backupRecordRepository.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordStatus.EXECUTING.ToString()); } - + else + Debug.WriteLine($"[{nameof(BackupBackgroundJob)}] Resource Group({resourceGroup.Id}) Bots are Busy, Running Bots: {resourceGroup.MaximumRunningBots}, waiting for available Bots...."); } + } - //Check if Any Delete - foreach (var rm in scheduleToDelete) - await _backupRecordRepository.RemoveWithFileAsync(rm); } + //Check if Any Delete + foreach (long backupRecordId in scheduleToDelete) + await _backupRecordRepository.RemoveWithFileAsync(backupRecordId); } catch (Exception ex) @@ -146,7 +140,7 @@ private void SetupBackgroundRemovedExpiredBackupsService(CancellationToken cance //Proceed List expiredBackups = (await _backupRecordRepository.GetAllExpiredAsync()) ?? []; //proceed - foreach (BackupRecord rm in expiredBackups.Take(50).ToList()) + foreach (BackupRecord rm in expiredBackups.OrderBy(x => x.Id).Take(50).ToList()) { //get relation List rmBackupRecords = (await _deliveryRecordRepository.GetAllByBackupRecordIdAsync(rm.Id)) ?? []; diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundZIPJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundZIPJob.cs index e7358c6..7daefae 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundZIPJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundZIPJob.cs @@ -5,6 +5,7 @@ using SemanticBackup.Infrastructure.BackgroundJobs.Bots; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -58,46 +59,39 @@ private void SetupBackgroundService(CancellationToken cancellationToken) try { //Proceed - List queuedBackups = await _backupRecordRepository.GetAllByStatusAsync(BackupRecordStatus.COMPLETED.ToString()); - if (queuedBackups != null && queuedBackups.Count > 0) + List queuedBackups = (await _backupRecordRepository.GetAllByStatusAsync(BackupRecordStatus.COMPLETED.ToString())) ?? []; + foreach (BackupRecord backupRecord in queuedBackups.OrderBy(x => x.Id).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 _resourceGroupRepository.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 _resourceGroupRepository.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 BackupZippingBot(resourceGroup.Id, backupRecord)); - bool updated = await _backupRecordRepository.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordStatus.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}"); - } + _logger.LogInformation("Queueing Zip Database Record Key: #{Id}...", backupRecord.Id); + //Add to Queue + _botsManagerBackgroundJob.AddBot(new BackupZippingBot(resourceGroup.Id, backupRecord)); + //Finally Update Status + _ = await _backupRecordRepository.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordStatus.COMPRESSING.ToString()); } else - { - _logger.LogInformation($">> Skipping Compression for Database Record Key: #{backupRecord.Id}..."); - bool updated = await _backupRecordRepository.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}"); - } + Debug.WriteLine($"[{nameof(BackupBackgroundZIPJob)}] Resource Group({resourceGroup.Id}) Bots are Busy, Running Bots: {resourceGroup.MaximumRunningBots}, waiting for available Bots...."); } 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: #{Id}...", backupRecord.Id); + //Finally Update Status + _ = await _backupRecordRepository.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordStatus.READY.ToString()); + } } + else + _logger.LogWarning("The Backup Record Id: {Id}, doesn't seem to have been assigned to a valid Resource Group, Zipping Skipped", backupRecord.Id); } } catch (Exception ex) diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs index b2a0977..f1aa24b 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs @@ -5,6 +5,7 @@ using SemanticBackup.Infrastructure.BackgroundJobs.Bots; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -61,89 +62,84 @@ private void SetupBackgroundService(CancellationToken cancellationToken) try { //Proceed - List contentDeliveryRecords = await _deliveryRecordRepository.GetAllByStatusAsync(BackupRecordDeliveryStatus.QUEUED.ToString()); - if (contentDeliveryRecords != null && contentDeliveryRecords.Count > 0) + List contentDeliveryRecords = (await _deliveryRecordRepository.GetAllByStatusAsync(BackupRecordDeliveryStatus.QUEUED.ToString())) ?? []; + //proceed + List scheduleToDeleteRecords = []; + foreach (BackupRecordDelivery contentDeliveryRecord in contentDeliveryRecords.OrderBy(x => x.Id).ToList()) { - List scheduleToDeleteRecords = []; - foreach (BackupRecordDelivery contentDeliveryRecord in contentDeliveryRecords.OrderBy(x => x.RegisteredDateUTC).ToList()) - { - _logger.LogInformation($"Processing Queued Content Delivery Record: #{contentDeliveryRecord.Id}..."); - BackupRecord backupRecordInfo = await _backupRecordRepository.GetByIdAsync(contentDeliveryRecord?.BackupRecordId ?? 0); - BackupDatabaseInfo backupDatabaseInfo = await _databaseInfoRepository.GetByIdAsync(backupRecordInfo?.BackupDatabaseInfoId); - ResourceGroup resourceGroup = await _resourceGroupRepository.GetByIdOrKeyAsync(backupDatabaseInfo?.ResourceGroupId); + _logger.LogInformation("Processing Queued Content Delivery Record: #{Id}...", contentDeliveryRecord.Id); + BackupRecord backupRecordInfo = await _backupRecordRepository.GetByIdAsync(contentDeliveryRecord?.BackupRecordId ?? 0); + BackupDatabaseInfo backupDatabaseInfo = await _databaseInfoRepository.GetByIdAsync(backupRecordInfo?.BackupDatabaseInfoId); + ResourceGroup resourceGroup = await _resourceGroupRepository.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: {BackupRecordId}, Content Delivery Record will be Deleted: {Id}", contentDeliveryRecord.BackupRecordId, contentDeliveryRecord.Id); + scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); + } + else if (resourceGroup == null) + { + _logger.LogWarning("Backup Record Id: {BackupRecordId}, Queued for Content Delivery has no valid Resource Group, Will be Removed", contentDeliveryRecord.BackupRecordId); + scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); + } + else if (resourceGroup.BackupDeliveryConfig == null) + { + _logger.LogWarning("Backup Record Id: {BackupRecordId}, Queued for Content Delivery has no valid Configuration, Will be Removed", contentDeliveryRecord.BackupRecordId); + scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); + } + else + { + if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, resourceGroup.MaximumRunningBots)) { - //Override Maximum Running Threads// This is because of currently being used exception - if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, resourceGroup.MaximumRunningBots)) + 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)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Ftp.ToString()) + { + //FTP Uploader + _botsManagerBackgroundJob.AddBot(new UploaderFTPBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Smtp.ToString()) + { + //Email Send and Uploader + _botsManagerBackgroundJob.AddBot(new UploaderEmailSMTPBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Dropbox.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)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Ftp.ToString()) - { - //FTP Uploader - _botsManagerBackgroundJob.AddBot(new UploaderFTPBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Smtp.ToString()) - { - //Email Send and Uploader - _botsManagerBackgroundJob.AddBot(new UploaderEmailSMTPBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Dropbox.ToString()) - { - //Email Send and Uploader - _botsManagerBackgroundJob.AddBot(new UploaderDropboxBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.AzureBlobStorage.ToString()) - { - //Azure Blob Storage - _botsManagerBackgroundJob.AddBot(new UploaderAzureStorageBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.ObjectStorage.ToString()) - { - //Object Storage - _botsManagerBackgroundJob.AddBot(new UploaderObjectStorageBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); - } - 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 _deliveryRecordRepository.UpdateStatusFeedAsync(contentDeliveryRecord.Id, status, statusMsg); - if (!updated) - _logger.LogWarning($"Queued for Backup but was unable to update backup record Key: #{contentDeliveryRecord.Id} status"); + //Email Send and Uploader + _botsManagerBackgroundJob.AddBot(new UploaderDropboxBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.AzureBlobStorage.ToString()) + { + //Azure Blob Storage + _botsManagerBackgroundJob.AddBot(new UploaderAzureStorageBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.ObjectStorage.ToString()) + { + //Object Storage + _botsManagerBackgroundJob.AddBot(new UploaderObjectStorageBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); } else - _logger.LogInformation($"Resource Group With Id: {resourceGroup.Id} Bots are Busy, Running Bots Count: {resourceGroup.MaximumRunningBots}, waiting for available Bots...."); + { + 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 + _ = await _deliveryRecordRepository.UpdateStatusFeedAsync(contentDeliveryRecord.Id, status, statusMsg); } + else + Debug.WriteLine($"[{nameof(BackupRecordDeliveryDispatchBackgroundJob)}] Resource Group({resourceGroup.Id}) Bots are Busy, Running Bots: {resourceGroup.MaximumRunningBots}, waiting for available Bots...."); } - //Check if Any Delete - foreach (string contentDeliveryRecordId in scheduleToDeleteRecords) - await _deliveryRecordRepository.RemoveAsync(contentDeliveryRecordId); } + //Check if Any Delete + foreach (string contentDeliveryRecordId in scheduleToDeleteRecords) + await _deliveryRecordRepository.RemoveAsync(contentDeliveryRecordId); } catch (Exception ex) { diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliverySchedulerBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliverySchedulerBackgroundJob.cs index d95b871..34c8ef3 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliverySchedulerBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliverySchedulerBackgroundJob.cs @@ -58,10 +58,10 @@ private void SetupBackgroundService(CancellationToken cancellationToken) try { //Proceed - List pendingExecutionRecords = await _backupRecordRepository.GetAllReadyAndPendingDeliveryAsync(); - foreach (BackupRecord backupRecord in pendingExecutionRecords?.OrderBy(x => x.RegisteredDateUTC)?.ToList()) + List pendingExecutionRecords = (await _backupRecordRepository.GetAllReadyAndPendingDeliveryAsync()) ?? []; + foreach (BackupRecord backupRecord in pendingExecutionRecords.OrderBy(x => x.Id).ToList()) { - _logger.LogInformation($"Queueing Content Delivery for Backup Record Id: {backupRecord.Id}..."); + _logger.LogInformation("Queueing Content Delivery for Backup Record Id: {Id}...", backupRecord.Id); //get db information BackupDatabaseInfo backupRecordDbInfo = await _databaseInfoRepository.GetByIdAsync(backupRecord.BackupDatabaseInfoId); //Check if valid Resource Group @@ -71,7 +71,7 @@ private void SetupBackgroundService(CancellationToken cancellationToken) //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"); + _logger.LogInformation("Resource Group Id: {Id}, doesn't have any backup delivery config, Skipped", backupRecord.Id); _ = await _backupRecordRepository.UpdateDeliveryRunnedAsync(backupRecord.Id, true, BackupRecordExecutedDeliveryRunStatus.SKIPPED_EXECUTION.ToString()); } else @@ -93,9 +93,9 @@ private void SetupBackgroundService(CancellationToken cancellationToken) //check if enabled if (isDeliveryEnabled) { - bool queuedSuccess = await _deliveryRecordRepository.AddOrUpdateAsync(new BackupRecordDelivery + _ = await _deliveryRecordRepository.AddOrUpdateAsync(new BackupRecordDelivery { - Id = $"{backupRecord.Id}|{resourceGroup.Id}|{deliveryType}".ToMD5String().ToUpper(), //Unique Identification + Id = $"{backupRecord.Id}|{resourceGroup.Id}|{deliveryType}".ToMD5String().ToUpper(), BackupRecordId = backupRecord.Id, CurrentStatus = BackupRecordDeliveryStatus.QUEUED.ToString(), DeliveryType = deliveryType.ToString(), @@ -103,8 +103,6 @@ private void SetupBackgroundService(CancellationToken cancellationToken) 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 diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupSchedulerBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupSchedulerBackgroundJob.cs index 839b6cd..7ad93db 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupSchedulerBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupSchedulerBackgroundJob.cs @@ -70,57 +70,53 @@ private void SetupBackgroundService(CancellationToken cancellationToken) { //Proceed DateTime currentTimeUTC = DateTime.UtcNow; - List dueSchedules = await _backupScheduleRepository.GetAllDueByDateAsync(); - if (dueSchedules != null && dueSchedules.Count > 0) + List dueSchedules = (await _backupScheduleRepository.GetAllDueByDateAsync()) ?? []; + //proceed + List scheduleToDelete = []; + foreach (BackupSchedule schedule in dueSchedules.OrderBy(x => x.Id).ToList()) { - List scheduleToDelete = []; - foreach (BackupSchedule schedule in dueSchedules.OrderBy(x => x.NextRunUTC).ToList()) + BackupDatabaseInfo backupDatabaseInfo = await _databaseInfoRepository.GetByIdAsync(schedule.BackupDatabaseInfoId); + if (backupDatabaseInfo == null) { - _logger.LogInformation($"Queueing Scheduled Backup..."); - BackupDatabaseInfo backupDatabaseInfo = await _databaseInfoRepository.GetByIdAsync(schedule.BackupDatabaseInfoId); - if (backupDatabaseInfo == null) + _logger.LogWarning("No Database Info matches with Id: {BackupDatabaseInfoId}, Schedule Record will be Deleted: {Id}", schedule.BackupDatabaseInfoId, schedule.Id); + scheduleToDelete.Add(schedule.Id); + } + else + { + //Proceed + ResourceGroup resourceGroup = await _resourceGroupRepository.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: {Id}, Reason: Assigned Resource Group doen't exist, Resource Group Id: {ResourceGroupId}, Schedule will be Removed", backupDatabaseInfo.Id, backupDatabaseInfo.ResourceGroupId); scheduleToDelete.Add(schedule.Id); } else { - //Proceed - ResourceGroup resourceGroup = await _resourceGroupRepository.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 = 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 - }; + 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 _backupRecordRepository.AddOrUpdateAsync(newRecord); - if (!addedSuccess) - throw new Exception($"Unable to Queue Database for Backup : {newRecord.Name}"); - //set last run - await _backupScheduleRepository.UpdateLastRunAsync(schedule.Id, currentTimeUTC); - } + //schedule run + _ = await _backupRecordRepository.AddOrUpdateAsync(newRecord); + //set last run + _ = await _backupScheduleRepository.UpdateLastRunAsync(schedule.Id, currentTimeUTC); } - } - //Check if Any Delete - foreach (string scheduleId in scheduleToDelete) - await _backupScheduleRepository.RemoveAsync(scheduleId); + } + //Check if Any Delete + foreach (string scheduleId in scheduleToDelete) + await _backupScheduleRepository.RemoveAsync(scheduleId); } catch (Exception ex) { @@ -139,6 +135,8 @@ private void SetupBackgroundNonResponsiveStopService(CancellationToken cancellat int executionTimeoutInMinutes = _persistanceOptions.ExecutionTimeoutInMinutes < 1 ? 1 : _persistanceOptions.ExecutionTimeoutInMinutes; while (!cancellationToken.IsCancellationRequested) { + //Delay + await Task.Delay(TimeSpan.FromMinutes(1)); try { //Proceed @@ -164,8 +162,7 @@ private void SetupBackgroundNonResponsiveStopService(CancellationToken cancellat _botsManagerBackgroundJob.TerminateBots(botsToRemove); } catch (Exception ex) { _logger.LogWarning($"Stopping Non Responsive Services Error: {ex.Message}"); } - //Delay - await Task.Delay(TimeSpan.FromMinutes(1)); + } }); t.Start(); diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs index 77bd2bd..550674f 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs @@ -73,7 +73,7 @@ await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed ElapsedMilliseconds = stopwatch.ElapsedMilliseconds, NewFilePath = newZIPPath, }, cancellationToken); - Console.WriteLine($"successfully zipped file: {_backupRecord.Path}"); + Status = BotStatus.Completed; } catch (Exception ex) diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs index 279596a..77c9e07 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs @@ -52,7 +52,7 @@ public async Task RunAsync(Func _logger; private readonly IBackupRecordRepository _backupRecordRepository; private readonly IContentDeliveryRecordRepository _deliveryRecordRepository; - private List Bots { get; set; } = []; public BotsManagerBackgroundJob(ILogger logger, IBackupRecordRepository backupRecordRepository, IContentDeliveryRecordRepository deliveryRecordRepository) diff --git a/SemanticBackup/Pages/ResourceGroups/DatabaseBackups/Details.cshtml b/SemanticBackup/Pages/ResourceGroups/DatabaseBackups/Details.cshtml index db58f64..133211c 100644 --- a/SemanticBackup/Pages/ResourceGroups/DatabaseBackups/Details.cshtml +++ b/SemanticBackup/Pages/ResourceGroups/DatabaseBackups/Details.cshtml @@ -161,15 +161,9 @@
-
Backup Expiry
- @if (Model.BackupRecordResponse.ExpiryDateUTC == null) - { - No Expiry - } - else - { - @string.Format("{0:yyyy-MM-dd HH:mm}", Model.BackupRecordResponse.ExpiryDateUTC.AdjustWithTimezoneOffset(User.GetUserTimeZoneOffset())) @string.Format("{0:N0} Day(s) left", ((DateTime)Model.BackupRecordResponse.ExpiryDateUTC - DateTime.UtcNow).TotalDays) - } +
Backup Expiry
+ @string.Format("{0:yyyy-MM-dd HH:mm}", Model.BackupRecordResponse.ExpiryDateUTC.AdjustWithTimezoneOffset(User.GetUserTimeZoneOffset())) + @(Model.BackupRecordResponse.ExpiryDateUTC.ToUtcDifferenceString())
From a18fac55727f0fa97db0394a62373eb682ace040 Mon Sep 17 00:00:00 2001 From: "George Njeri (Swagfin)" Date: Sun, 29 Jun 2025 15:36:50 +0300 Subject: [PATCH 2/4] v5.2.1.2 --- .../BackgroundJobs/BackupBackgroundJob.cs | 20 ++--- .../BackgroundJobs/BackupBackgroundZIPJob.cs | 17 ++-- ...ckupRecordDeliveryDispatchBackgroundJob.cs | 83 +++++++++---------- .../BackgroundJobs/Bots/BackupZippingBot.cs | 2 +- .../Bots/InDepthDeleteDropboxBot.cs | 2 +- .../Bots/InDepthDeleteObjectStorageBot.cs | 2 +- .../BackgroundJobs/Bots/MySQLBackupBot.cs | 2 +- .../BackgroundJobs/Bots/SQLBackupBot.cs | 2 +- .../Bots/UploaderAzureStorageBot.cs | 2 +- .../BackgroundJobs/Bots/UploaderDropboxBot.cs | 2 +- .../Bots/UploaderEmailSMTPBot.cs | 2 +- .../BackgroundJobs/Bots/UploaderFTPBot.cs | 2 +- .../BackgroundJobs/Bots/UploaderLinkGenBot.cs | 2 +- .../Bots/UploaderObjectStorageBot.cs | 2 +- .../BackgroundJobs/Bots/_IBot.cs | 2 +- .../BotsManagerBackgroundJob.cs | 37 +++++---- SemanticBackup/SemanticBackup.csproj | 4 +- 17 files changed, 86 insertions(+), 99 deletions(-) diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs index 7be207a..57a3369 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs @@ -6,7 +6,6 @@ using SemanticBackup.Infrastructure.BackgroundJobs.Bots; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -97,19 +96,14 @@ private void SetupBackgroundService(CancellationToken cancellationToken) } else { - if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, resourceGroup.MaximumRunningBots)) - { - if (resourceGroup.DbType.Contains("SQLSERVER")) - _botsManagerBackgroundJob.AddBot(new SQLBackupBot(backupDatabaseInfo.DatabaseName, resourceGroup, backupRecord, _providerForSQLServer)); - else if (resourceGroup.DbType.Contains("MYSQL") || resourceGroup.DbType.Contains("MARIADB")) - _botsManagerBackgroundJob.AddBot(new MySQLBackupBot(backupDatabaseInfo.DatabaseName, resourceGroup, backupRecord, _providerForMySqlServer)); - else - throw new Exception($"No Bot is registered to Handle Database Backups of Type: {resourceGroup.DbType}"); - //Finally Update Status - _ = await _backupRecordRepository.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordStatus.EXECUTING.ToString()); - } + if (resourceGroup.DbType.Contains("SQLSERVER")) + _botsManagerBackgroundJob.AddBot(new SQLBackupBot(backupDatabaseInfo.DatabaseName, resourceGroup, backupRecord, _providerForSQLServer)); + else if (resourceGroup.DbType.Contains("MYSQL") || resourceGroup.DbType.Contains("MARIADB")) + _botsManagerBackgroundJob.AddBot(new MySQLBackupBot(backupDatabaseInfo.DatabaseName, resourceGroup, backupRecord, _providerForMySqlServer)); else - Debug.WriteLine($"[{nameof(BackupBackgroundJob)}] Resource Group({resourceGroup.Id}) Bots are Busy, Running Bots: {resourceGroup.MaximumRunningBots}, waiting for available Bots...."); + throw new Exception($"No Bot is registered to Handle Database Backups of Type: {resourceGroup.DbType}"); + //Finally Update Status + _ = await _backupRecordRepository.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordStatus.EXECUTING.ToString()); } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundZIPJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundZIPJob.cs index 7daefae..02332eb 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundZIPJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundZIPJob.cs @@ -5,7 +5,6 @@ using SemanticBackup.Infrastructure.BackgroundJobs.Bots; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -71,17 +70,11 @@ private void SetupBackgroundService(CancellationToken cancellationToken) //Use Resource Group Threads if (resourceGroup.CompressBackupFiles) { - //Check Resource Group Maximum Threads - if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, resourceGroup.MaximumRunningBots)) - { - _logger.LogInformation("Queueing Zip Database Record Key: #{Id}...", backupRecord.Id); - //Add to Queue - _botsManagerBackgroundJob.AddBot(new BackupZippingBot(resourceGroup.Id, backupRecord)); - //Finally Update Status - _ = await _backupRecordRepository.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordStatus.COMPRESSING.ToString()); - } - else - Debug.WriteLine($"[{nameof(BackupBackgroundZIPJob)}] Resource Group({resourceGroup.Id}) Bots are Busy, Running Bots: {resourceGroup.MaximumRunningBots}, waiting for available Bots...."); + _logger.LogInformation("Queueing Zip Database Record Key: #{Id}...", backupRecord.Id); + //Add to Queue + _botsManagerBackgroundJob.AddBot(new BackupZippingBot(resourceGroup.Id, backupRecord)); + //Finally Update Status + _ = await _backupRecordRepository.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordStatus.COMPRESSING.ToString()); } else { diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs index f1aa24b..c04fbc1 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs @@ -5,7 +5,6 @@ using SemanticBackup.Infrastructure.BackgroundJobs.Bots; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -89,52 +88,48 @@ private void SetupBackgroundService(CancellationToken cancellationToken) } else { - if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, resourceGroup.MaximumRunningBots)) + + 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)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Ftp.ToString()) + { + //FTP Uploader + _botsManagerBackgroundJob.AddBot(new UploaderFTPBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Smtp.ToString()) + { + //Email Send and Uploader + _botsManagerBackgroundJob.AddBot(new UploaderEmailSMTPBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Dropbox.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)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Ftp.ToString()) - { - //FTP Uploader - _botsManagerBackgroundJob.AddBot(new UploaderFTPBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Smtp.ToString()) - { - //Email Send and Uploader - _botsManagerBackgroundJob.AddBot(new UploaderEmailSMTPBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Dropbox.ToString()) - { - //Email Send and Uploader - _botsManagerBackgroundJob.AddBot(new UploaderDropboxBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.AzureBlobStorage.ToString()) - { - //Azure Blob Storage - _botsManagerBackgroundJob.AddBot(new UploaderAzureStorageBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.ObjectStorage.ToString()) - { - //Object Storage - _botsManagerBackgroundJob.AddBot(new UploaderObjectStorageBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); - } - 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 - _ = await _deliveryRecordRepository.UpdateStatusFeedAsync(contentDeliveryRecord.Id, status, statusMsg); + //Email Send and Uploader + _botsManagerBackgroundJob.AddBot(new UploaderDropboxBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.AzureBlobStorage.ToString()) + { + //Azure Blob Storage + _botsManagerBackgroundJob.AddBot(new UploaderAzureStorageBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.ObjectStorage.ToString()) + { + //Object Storage + _botsManagerBackgroundJob.AddBot(new UploaderObjectStorageBot(resourceGroup, backupRecordInfo, contentDeliveryRecord)); } else - Debug.WriteLine($"[{nameof(BackupRecordDeliveryDispatchBackgroundJob)}] Resource Group({resourceGroup.Id}) Bots are Busy, Running Bots: {resourceGroup.MaximumRunningBots}, waiting for available Bots...."); + { + 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 + _ = await _deliveryRecordRepository.UpdateStatusFeedAsync(contentDeliveryRecord.Id, status, statusMsg); } } //Check if Any Delete diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs index 550674f..51df406 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs @@ -16,7 +16,7 @@ internal class BackupZippingBot : IBot public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; public string BotId => $"{_resourceGroupId}::{_backupRecord.Id}::{nameof(BackupZippingBot)}"; public string ResourceGroupId => _resourceGroupId; - public BotStatus Status { get; internal set; } = BotStatus.NotReady; + public BotStatus Status { get; internal set; } = BotStatus.PendingStart; public BackupZippingBot(string resourceGroupId, BackupRecord backupRecord) { diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs index 77c9e07..7430c49 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs @@ -16,7 +16,7 @@ internal class InDepthDeleteDropboxBot : IBot 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 BotStatus Status { get; internal set; } = BotStatus.PendingStart; public InDepthDeleteDropboxBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord) { diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteObjectStorageBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteObjectStorageBot.cs index e15f7d2..33ccb93 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteObjectStorageBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteObjectStorageBot.cs @@ -17,7 +17,7 @@ internal class InDepthDeleteObjectStorageBot : IBot 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 BotStatus Status { get; internal set; } = BotStatus.PendingStart; public InDepthDeleteObjectStorageBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord) { diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/MySQLBackupBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/MySQLBackupBot.cs index d7e21b3..ec613a1 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/MySQLBackupBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/MySQLBackupBot.cs @@ -17,7 +17,7 @@ internal class MySQLBackupBot : IBot 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 BotStatus Status { get; internal set; } = BotStatus.PendingStart; public MySQLBackupBot(string databaseName, ResourceGroup resourceGroup, BackupRecord backupRecord, IBackupProviderForMySQLServer providerForMySQLServer) { diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLBackupBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLBackupBot.cs index e6bb94f..530490d 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLBackupBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLBackupBot.cs @@ -17,7 +17,7 @@ internal class SQLBackupBot : IBot 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 BotStatus Status { get; internal set; } = BotStatus.PendingStart; public SQLBackupBot(string databaseName, ResourceGroup resourceGroup, BackupRecord backupRecord, IBackupProviderForSQLServer providerForSQLServer) { diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs index 5a126bf..e319a3a 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs @@ -16,7 +16,7 @@ internal class UploaderAzureStorageBot : IBot 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 BotStatus Status { get; internal set; } = BotStatus.PendingStart; public UploaderAzureStorageBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord) { diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderDropboxBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderDropboxBot.cs index 3c74aa5..ff82467 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderDropboxBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderDropboxBot.cs @@ -17,7 +17,7 @@ internal class UploaderDropboxBot : IBot 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 BotStatus Status { get; internal set; } = BotStatus.PendingStart; public UploaderDropboxBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord) { diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderEmailSMTPBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderEmailSMTPBot.cs index fdc2432..b12ea18 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderEmailSMTPBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderEmailSMTPBot.cs @@ -19,7 +19,7 @@ internal class UploaderEmailSMTPBot : IBot 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 BotStatus Status { get; internal set; } = BotStatus.PendingStart; public UploaderEmailSMTPBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord) { diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderFTPBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderFTPBot.cs index bc1ef24..3ce238e 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderFTPBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderFTPBot.cs @@ -16,7 +16,7 @@ internal class UploaderFTPBot : IBot 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 BotStatus Status { get; internal set; } = BotStatus.PendingStart; public UploaderFTPBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord) { diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderLinkGenBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderLinkGenBot.cs index 0533c1e..cfa4631 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderLinkGenBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderLinkGenBot.cs @@ -16,7 +16,7 @@ internal class UploaderLinkGenBot : IBot 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 BotStatus Status { get; internal set; } = BotStatus.PendingStart; public UploaderLinkGenBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord) { diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderObjectStorageBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderObjectStorageBot.cs index 1aa84fa..72569e9 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderObjectStorageBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderObjectStorageBot.cs @@ -18,7 +18,7 @@ internal class UploaderObjectStorageBot : IBot 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 BotStatus Status { get; internal set; } = BotStatus.PendingStart; public UploaderObjectStorageBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord) { diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/_IBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/_IBot.cs index f7e01d7..5c71126 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/_IBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/_IBot.cs @@ -33,7 +33,7 @@ public enum DeliveryFeedType public enum BotStatus { - NotReady, + PendingStart, Starting, Running, Completed, diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs index 8516a52..8c1ea8a 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs @@ -1,9 +1,11 @@ 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.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -13,13 +15,15 @@ namespace SemanticBackup.Infrastructure.BackgroundJobs public class BotsManagerBackgroundJob : IHostedService { private readonly ILogger _logger; + private readonly IResourceGroupRepository _resourceGroupRepository; private readonly IBackupRecordRepository _backupRecordRepository; private readonly IContentDeliveryRecordRepository _deliveryRecordRepository; private List Bots { get; set; } = []; - public BotsManagerBackgroundJob(ILogger logger, IBackupRecordRepository backupRecordRepository, IContentDeliveryRecordRepository deliveryRecordRepository) + public BotsManagerBackgroundJob(ILogger logger, IResourceGroupRepository resourceGroupRepository, IBackupRecordRepository backupRecordRepository, IContentDeliveryRecordRepository deliveryRecordRepository) { _logger = logger; + _resourceGroupRepository = resourceGroupRepository; _backupRecordRepository = backupRecordRepository; _deliveryRecordRepository = deliveryRecordRepository; } @@ -47,39 +51,40 @@ public void TerminateBots(List botIds) } } - public bool HasAvailableResourceGroupBotsCount(string resourceGroupId, int maximumThreads = 1) - { - return Bots.Count(x => x.ResourceGroupId == resourceGroupId) < maximumThreads; - } - private void SetupBotsBackgroundService(CancellationToken cancellationToken) { Thread t = new(async () => { while (!cancellationToken.IsCancellationRequested) { + //Delay + await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken); try { - if (Bots.Count > 0) + //Remove Bots with status: [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); + + //Start Bots with status: [PendingStart] + List botsNotReady = (Bots.Where(x => x.Status == BotStatus.PendingStart).OrderBy(x => x.DateCreatedUtc).ToList()) ?? []; + foreach (IBot bot in botsNotReady) { - //Start not ready bots - List botsNotReady = (Bots.Where(x => x.Status == BotStatus.NotReady).OrderBy(x => x.DateCreatedUtc).ToList()) ?? []; - foreach (IBot bot in botsNotReady) + ResourceGroup resourceGroup = await _resourceGroupRepository.GetByIdOrKeyAsync(bot.ResourceGroupId ?? string.Empty); + int runningPods = Bots.Count(x => x.ResourceGroupId == resourceGroup.Id && x.Status != BotStatus.PendingStart); + if (runningPods < resourceGroup.MaximumRunningBots) { + Debug.WriteLine($"Running bot #{bot.BotId}"); _ = bot.RunAsync(OnDeliveryFeedUpdate, cancellationToken); } - //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); + else + Debug.WriteLine($"[{nameof(BotsManagerBackgroundJob)}] Resource Group({resourceGroup.Id}) {runningPods}-Bot(s) are Busy, maximum pods configured to: {resourceGroup.MaximumRunningBots}, waiting for available Bots...."); } } catch (Exception ex) { _logger.LogWarning("Error: {Message}", ex.Message); } - //Delay - await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); } }); t.Start(); diff --git a/SemanticBackup/SemanticBackup.csproj b/SemanticBackup/SemanticBackup.csproj index 60de147..036c7e9 100644 --- a/SemanticBackup/SemanticBackup.csproj +++ b/SemanticBackup/SemanticBackup.csproj @@ -4,8 +4,8 @@ net8.0 98e83838-8ab0-44d3-a023-52d80ba01705 Linux - 5.2.1.1 - 5.2.1.1 + 5.2.1.2 + 5.2.1.2 From eaba4c86e3ae72e22ed9972d18c91299d4508e54 Mon Sep 17 00:00:00 2001 From: "George Njeri (Swagfin)" Date: Sun, 29 Jun 2025 15:50:02 +0300 Subject: [PATCH 3/4] chore: logging on bots --- .../BackgroundJobs/Bots/BackupZippingBot.cs | 2 +- .../BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs | 2 +- .../BackgroundJobs/Bots/InDepthDeleteObjectStorageBot.cs | 2 +- .../BackgroundJobs/Bots/MySQLBackupBot.cs | 2 +- .../BackgroundJobs/Bots/SQLBackupBot.cs | 2 +- .../BackgroundJobs/Bots/UploaderAzureStorageBot.cs | 2 +- .../BackgroundJobs/Bots/UploaderDropboxBot.cs | 2 +- .../BackgroundJobs/Bots/UploaderEmailSMTPBot.cs | 2 +- .../BackgroundJobs/Bots/UploaderFTPBot.cs | 2 +- .../BackgroundJobs/Bots/UploaderLinkGenBot.cs | 2 +- .../BackgroundJobs/Bots/UploaderObjectStorageBot.cs | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs index 51df406..7833245 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs @@ -79,7 +79,7 @@ await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed catch (Exception ex) { Status = BotStatus.Error; - Console.WriteLine(ex.Message); + Console.WriteLine($"[Error] {nameof(BackupZippingBot)}: {ex.Message}"); stopwatch.Stop(); //notify update await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs index 7430c49..d4cb7d3 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs @@ -58,7 +58,7 @@ public async Task RunAsync(Func Date: Sun, 29 Jun 2025 16:10:45 +0300 Subject: [PATCH 4/4] chore: Added with Retry Policy --- .../BackgroundJobs/Bots/BackupZippingBot.cs | 11 ++- .../Bots/InDepthDeleteDropboxBot.cs | 29 +++--- .../Bots/InDepthDeleteObjectStorageBot.cs | 28 +++--- .../BackgroundJobs/Bots/MySQLBackupBot.cs | 16 ++-- .../BackgroundJobs/Bots/SQLBackupBot.cs | 16 ++-- .../Bots/UploaderAzureStorageBot.cs | 24 ++--- .../BackgroundJobs/Bots/UploaderDropboxBot.cs | 31 ++++--- .../Bots/UploaderEmailSMTPBot.cs | 10 ++- .../BackgroundJobs/Bots/UploaderFTPBot.cs | 89 ++++++++++--------- .../BackgroundJobs/Bots/UploaderLinkGenBot.cs | 18 +++- .../Bots/UploaderObjectStorageBot.cs | 21 +++-- 11 files changed, 180 insertions(+), 113 deletions(-) diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs index 7833245..acbe05a 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs @@ -36,11 +36,12 @@ public async Task RunAsync(Func { + using ZipOutputStream s = new(File.Create(newZIPPath)); s.SetLevel(9); // 0 - store only to 9 - means best compression byte[] buffer = new byte[4096]; ZipEntry entry = new(Path.GetFileName(_backupRecord.Path)) @@ -59,7 +60,11 @@ public async Task RunAsync(Func { + //Directory + 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(settings.AccessToken.Trim()); string initialFileName = string.Format("{0}{1}", validDirectory, fileName); Dropbox.Api.Files.DeleteResult delResponse = await dbx.Files.DeleteV2Async(initialFileName, null); - } + + }, maxRetries: 2, delay: TimeSpan.FromSeconds(5), cancellationToken: cancellationToken); + stopwatch.Stop(); Status = BotStatus.Completed; diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteObjectStorageBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteObjectStorageBot.cs index ea5f6c9..1e69abd 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteObjectStorageBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteObjectStorageBot.cs @@ -1,5 +1,6 @@ using Minio; using Minio.DataModel.Args; +using SemanticBackup.Core.Helpers; using SemanticBackup.Core.Models; using System; using System.Diagnostics; @@ -37,17 +38,24 @@ public async Task RunAsync(Func { - await minioClient.RemoveObjectAsync(new RemoveObjectArgs() - .WithBucket(validBucket) - .WithObject(fileName), cancellationToken); - } + //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); + } + + }, maxRetries: 2, delay: TimeSpan.FromSeconds(5), cancellationToken: cancellationToken); + + stopwatch.Stop(); Status = BotStatus.Completed; diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/MySQLBackupBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/MySQLBackupBot.cs index c185b2b..bbcf916 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/MySQLBackupBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/MySQLBackupBot.cs @@ -1,4 +1,5 @@ -using SemanticBackup.Core.Interfaces; +using SemanticBackup.Core.Helpers; +using SemanticBackup.Core.Interfaces; using SemanticBackup.Core.Models; using System; using System.Diagnostics; @@ -40,11 +41,16 @@ public async Task RunAsync(Func + { + //Execute Service + if (!await _providerForMySQLServer.BackupDatabaseAsync(_databaseName, _resourceGroup, _backupRecord)) + throw new Exception("Creating Backup Failed to Return Success Completion"); + + }, maxRetries: 2, delay: TimeSpan.FromSeconds(5), cancellationToken: cancellationToken); + stopwatch.Stop(); - if (!backupedUp) - throw new Exception("Creating Backup Failed to Return Success Completion"); //notify update await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed { diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLBackupBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLBackupBot.cs index 15a21c7..5ebc067 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLBackupBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLBackupBot.cs @@ -1,4 +1,5 @@ -using SemanticBackup.Core.Interfaces; +using SemanticBackup.Core.Helpers; +using SemanticBackup.Core.Interfaces; using SemanticBackup.Core.Models; using System; using System.Diagnostics; @@ -40,12 +41,17 @@ public async Task RunAsync(Func + { + + //Execute Service + if (!await _providerForSQLServer.BackupDatabaseAsync(_databaseName, _resourceGroup, _backupRecord)) + throw new Exception("Creating Backup Failed to Return Success Completion"); + + }, maxRetries: 2, delay: TimeSpan.FromSeconds(5), cancellationToken: cancellationToken); stopwatch.Stop(); - if (!backupedUp) - throw new Exception("Creating Backup Failed to Return Success Completion"); //notify update await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed { diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs index eff2c54..e86df5d 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs @@ -1,4 +1,5 @@ using Azure.Storage.Blobs; +using SemanticBackup.Core.Helpers; using SemanticBackup.Core.Models; using System; using System.Diagnostics; @@ -41,21 +42,24 @@ public async Task RunAsync(Func { + //Container + 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(_backupRecord.Path, FileMode.Open); BlobContainerClient containerClient = new(settings.ConnectionString, validContainer); BlobClient blobClient = containerClient.GetBlobClient(fileName); _ = await blobClient.UploadAsync(stream, true, cancellationToken); executionMessage = $"Uploaded to Container: {validContainer}"; - } + + }, maxRetries: 2, delay: TimeSpan.FromSeconds(5), cancellationToken: cancellationToken); + stopwatch.Stop(); //notify update await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderDropboxBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderDropboxBot.cs index 2a0ba38..3bd5ea4 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderDropboxBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderDropboxBot.cs @@ -1,5 +1,6 @@ using Dropbox.Api; using Dropbox.Api.Files; +using SemanticBackup.Core.Helpers; using SemanticBackup.Core.Models; using System; using System.Diagnostics; @@ -41,22 +42,26 @@ public async Task RunAsync(Func { + //Directory + 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(settings.AccessToken.Trim()); + using MemoryStream mem = new(await File.ReadAllBytesAsync(this._backupRecord.Path, cancellationToken)); FileMetadata updated = await dbx.Files.UploadAsync(string.Format("{0}{1}", validDirectory, fileName), WriteMode.Overwrite.Instance, body: mem); executionMessage = $"Uploaded to: {validDirectory}"; - } + + }, maxRetries: 2, delay: TimeSpan.FromSeconds(5), cancellationToken: cancellationToken); + stopwatch.Stop(); //notify update await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderEmailSMTPBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderEmailSMTPBot.cs index 844cc54..ef337b4 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderEmailSMTPBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderEmailSMTPBot.cs @@ -1,4 +1,5 @@ using SemanticBackup.Core; +using SemanticBackup.Core.Helpers; using SemanticBackup.Core.Models; using System; using System.Collections.Generic; @@ -48,8 +49,10 @@ public async Task RunAsync(Func { + using MailMessage e_mail = new(); using SmtpClient Smtp_Server = new(); //Configs Smtp_Server.UseDefaultCredentials = false; @@ -89,7 +92,10 @@ public async Task RunAsync(Func { - string fullServerUrl = $"ftp://{validServerName}{validDirectory}{fileName}"; - byte[] fileContents; - using (FileStream sourceStream = File.OpenRead(this._backupRecord.Path)) + //Directory + string validDirectory = (string.IsNullOrWhiteSpace(settings.Directory)) ? "/" : settings.Directory; + 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); + //Proceed + try { - fileContents = new byte[sourceStream.Length]; - await sourceStream.ReadAsync(fileContents, cancellationToken); - } + string fullServerUrl = $"ftp://{validServerName}{validDirectory}{fileName}"; + byte[] fileContents; + using (FileStream sourceStream = File.OpenRead(this._backupRecord.Path)) + { + fileContents = new byte[sourceStream.Length]; + await sourceStream.ReadAsync(fileContents, cancellationToken); + } #pragma warning disable SYSLIB0014 // Type or member is obsolete - FtpWebRequest request = (FtpWebRequest)WebRequest.Create(fullServerUrl); + FtpWebRequest request = (FtpWebRequest)WebRequest.Create(fullServerUrl); #pragma warning restore SYSLIB0014 // Type or member is obsolete - request.Method = WebRequestMethods.Ftp.UploadFile; - request.Credentials = new NetworkCredential(settings.Username, settings.Password); - request.EnableSsl = false; // Set to true if your FTP server uses FTPS - request.UsePassive = true; - request.UseBinary = true; - request.KeepAlive = false; - request.ContentLength = fileContents.Length; + request.Method = WebRequestMethods.Ftp.UploadFile; + request.Credentials = new NetworkCredential(settings.Username, settings.Password); + request.EnableSsl = false; // Set to true if your FTP server uses FTPS + request.UsePassive = true; + request.UseBinary = true; + request.KeepAlive = false; + request.ContentLength = fileContents.Length; - // Write to the request stream - using (Stream requestStream = await request.GetRequestStreamAsync()) - { - await requestStream.WriteAsync(fileContents, 0, fileContents.Length, cancellationToken); - } + // Write to the request stream + using (Stream requestStream = await request.GetRequestStreamAsync()) + { + await requestStream.WriteAsync(fileContents, 0, fileContents.Length, cancellationToken); + } - // Get the response to ensure upload completed - using FtpWebResponse response = (FtpWebResponse)await request.GetResponseAsync(); - if (response.StatusCode == FtpStatusCode.ClosingData) - { - executionMessage = $"Uploaded to Server: {settings.Server}"; + // Get the response to ensure upload completed + using FtpWebResponse response = (FtpWebResponse)await request.GetResponseAsync(); + if (response.StatusCode == FtpStatusCode.ClosingData) + { + executionMessage = $"Uploaded to Server: {settings.Server}"; + } + else + { + throw new Exception($"Failed to upload. FTP status: {response.StatusDescription}"); + } } - else + catch (Exception ex) { - throw new Exception($"Failed to upload. FTP status: {response.StatusDescription}"); + throw new Exception($"Upload failed: {ex.Message}"); } - } - catch (Exception ex) - { - throw new Exception($"Upload failed: {ex.Message}"); - } + + }, maxRetries: 2, delay: TimeSpan.FromSeconds(5), cancellationToken: cancellationToken); + stopwatch.Stop(); //notify update await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderLinkGenBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderLinkGenBot.cs index d4e23eb..fadccc7 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderLinkGenBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderLinkGenBot.cs @@ -1,5 +1,6 @@ using SemanticBackup.Core; using SemanticBackup.Core.Extensions; +using SemanticBackup.Core.Helpers; using SemanticBackup.Core.Models; using System; using System.Diagnostics; @@ -34,10 +35,19 @@ public async Task RunAsync(Func + { + //get download link:: + string contentLink = 5.GenerateUniqueId(); + if (settings.DownloadLinkType == "LONG") + contentLink = string.Format("{0}?token={1}", 55.GenerateUniqueId(), $"{_backupRecord.Id}|{_resourceGroup.Id}".ToMD5String()); + + return Task.CompletedTask; + + }, maxRetries: 2, delay: TimeSpan.FromSeconds(5), cancellationToken: cancellationToken); + stopwatch.Stop(); //notify update await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderObjectStorageBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderObjectStorageBot.cs index 69fa67b..77e8442 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderObjectStorageBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderObjectStorageBot.cs @@ -1,6 +1,7 @@ using Minio; using Minio.DataModel.Args; using Minio.DataModel.Response; +using SemanticBackup.Core.Helpers; using SemanticBackup.Core.Models; using System; using System.Diagnostics; @@ -43,14 +44,16 @@ public async Task RunAsync(Func { + //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 PutObjectResponse putResponse = await minioClient.PutObjectAsync(new PutObjectArgs() .WithBucket(settings.Bucket) @@ -65,7 +68,9 @@ public async Task RunAsync(Func