1 Commits

Author SHA1 Message Date
Bobbie Hodgetts 89ec6476fc Added sql server ping feature, check for online server (#51) 2026-05-11 22:21:39 +01:00
10 changed files with 188 additions and 195 deletions
+46 -60
View File
@@ -1,92 +1,78 @@
using System;
using FikaAmazonAPI.AmazonSpApiSDK.Models.Services;
using System;
using System.Configuration;
namespace bnhtrade.Core.Data.Database
{
/// this class needs a sort out. Ideally it shoud be called what it is, a connection string builder, and
/// it should expose a method to serve the connection string--rather than class inheritance and using a property setter
/// something to do once there aren't so many open git branches
///
public class Connection
{
//protected readonly string SqlConnectionString;
private Model.Credentials.bnhtradeDB _dbCredentials;
private string _server;
private string _user;
private string _userPassword;
private string _database = "e2A";
private bool _persistSecurityInfo = true;
private bool _multipleActiveResultSets = true;
private bool _encrypt = true;
private uint _connectRetryInterval;
private uint _connectRetryCount;
private uint _connectTimeout;
protected string SqlConnectionString
{
get { return _dbCredentials.ConnectionString; }
}
internal string SqlConnectionString { get; private set; }
public Connection()
/// <summary>
///
/// </summary>
/// <param name="connectRetryInterval">Retry interval in seconds, must be 5-60</param>
/// <param name="connectTimeout">Timeout length in seconds (0 is indefinitely)</param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="Exception"></exception>
public Connection(uint connectRetryInterval = 10, uint connectRetryCount = 6, uint connectionTimeout = 60)
{
if (connectRetryInterval < 5 || connectRetryInterval > 60)
{
// these are limits set by the sql server
throw new ArgumentOutOfRangeException("ConnectRetryInterval must be from 5 to 60 seconds");
}
_connectRetryInterval = connectRetryInterval;
_connectRetryCount = connectRetryCount;
_connectTimeout = connectionTimeout;
// retrive credentials from app.local.config
var config = new Config().GetConfiguration();
// attempt to retrive credentials from app.local.config
try
{
string dataSource = config.AppSettings.Settings["DbDataSource"].Value;
string userId = config.AppSettings.Settings["DbUserId"].Value;
string pass = config.AppSettings.Settings["DbUserPassword"].Value;
_server = config.AppSettings.Settings["DbDataSource"].Value;
_user = config.AppSettings.Settings["DbUserId"].Value;
_userPassword = config.AppSettings.Settings["DbUserPassword"].Value;
// check
if (string.IsNullOrEmpty(dataSource))
if (string.IsNullOrEmpty(_server))
{
throw new ArgumentException("Could not retrive 'DbDataSource' from config file");
}
else if (string.IsNullOrEmpty(userId))
else if (string.IsNullOrEmpty(_user))
{
throw new ArgumentException("Could not retrive 'DbUserId' from config file");
}
else if (string.IsNullOrEmpty(pass))
else if (string.IsNullOrEmpty(_userPassword))
{
throw new ArgumentException("Could not retrive 'DbUserPassword' from config file");
}
var dbCredentials = new bnhtrade.Core.Model.Credentials.bnhtradeDB(dataSource, userId, pass);
this._dbCredentials = dbCredentials;
}
catch (Exception ex)
{
throw new Exception("Unable to retirve DB credentials: " + ex.Message);
}
}
private void ConnectionOld()
{
// attempt to retrive credentials from app.local.config
try
{
string dataSource = ConfigurationManager.AppSettings["DbDataSource"];
string userId = ConfigurationManager.AppSettings["DbUserId"];
string pass = ConfigurationManager.AppSettings["DbUserPassword"];
// check
if (string.IsNullOrEmpty(dataSource))
{
throw new ArgumentException("Could not retrive 'DbDataSource' from config file");
}
else if (string.IsNullOrEmpty(userId))
{
throw new ArgumentException("Could not retrive 'DbUserId' from config file");
}
else if (string.IsNullOrEmpty(pass))
{
throw new ArgumentException("Could not retrive 'DbUserPassword' from config file");
}
var dbCredentials = new bnhtrade.Core.Model.Credentials.bnhtradeDB(dataSource, userId, pass);
this._dbCredentials = dbCredentials;
}
catch(Exception ex)
{
throw new Exception("Unable to retirve DB credentials: " + ex.Message);
}
}
public Connection(Model.Credentials.bnhtradeDB dbCredentials)
{
// setup sql parameters
if (dbCredentials == null)
{
throw new Exception("DB credentials object is null");
}
this._dbCredentials = dbCredentials;
// build connection string
SqlConnectionString = "Server=" + _server + ";Database=" + _database + ";PersistSecurityInfo=" + _persistSecurityInfo.ToString()
+ ";User=" + _user + ";Password=" + _userPassword + ";MultipleActiveResultSets=" + _multipleActiveResultSets.ToString()
+ ";ConnectRetryInterval=" + _connectRetryInterval + ";ConnectRetryCount=" + _connectRetryCount + ";Timeout=" + _connectTimeout
+ ";Encrypt=" + _encrypt.ToString() +";";
}
}
}
@@ -10,11 +10,6 @@ namespace bnhtrade.Core.Data.Database.Sku.Price
{
public class ReadParameter : Connection
{
public ReadParameter() : base()
{
}
public List<Model.Sku.Price.SkuRepriceInfo> Execute()
{
string stringSql = @"
@@ -0,0 +1,87 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.SqlClient;
namespace bnhtrade.Core.Data.Database
{
internal class ServerPing
{
internal ServerPing()
{
}
internal Logic.Log.LogEvent log = new Logic.Log.LogEvent();
/// <summary>
/// Polls SQL Server until it comes online, max attempts are reached, or the cancellation token is triggered.
/// </summary>
/// <param name="timeout">Seconds each connection attempt waits before failing.</param>
/// <param name="maxAttempts">Maximum number of poll attempts. 0 = retry indefinitely.</param>
/// <param name="cancellationToken">Token to cancel the polling loop.</param>
/// <returns>True when the server responds, false if max attempts reached or cancelled.</returns>
internal async Task<bool> WaitForServerAsync(uint timeout = 10, uint maxAttempts = 6, CancellationToken cancellationToken = default)
{
var connection = new Connection(connectionTimeout: timeout);
int attempt = 0;
DateTime firstAttempt = DateTime.UtcNow;
int lineWidth = Console.WindowWidth - 1;
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Starting to poll SQL Server for availability...");
while (!cancellationToken.IsCancellationRequested)
{
attempt++;
bool isFinalAttempt = maxAttempts > 0 && attempt >= maxAttempts;
Console.WriteLine($"\r[{DateTime.Now:HH:mm:ss}] Attempt {attempt} of {maxAttempts} ");
try
{
using SqlConnection conn = new SqlConnection(connection.SqlConnectionString);
if (attempt == 1)
firstAttempt = DateTime.UtcNow;
await conn.OpenAsync(cancellationToken);
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] SQL Server is online, attempt {attempt} succeeded!");
return true;
}
catch (OperationCanceledException)
{
Console.WriteLine($"\n[{DateTime.Now:HH:mm:ss}] Polling cancelled.");
return false;
}
catch (SqlException ex)
{
if (isFinalAttempt)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] SQL Server not available (error {ex.Number}): {ex.Message}");
}
}
if (isFinalAttempt)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Max attempts ({maxAttempts}) reached, could not connect to SQL Server.");
return false;
}
// countdown overwrites the same line as the attempt message
int retrySeconds = (firstAttempt.AddSeconds(timeout * attempt) - DateTime.UtcNow).Seconds ;
for (int i = retrySeconds; i > 0; i--)
{
Console.Write($"\r[--------] Retrying in {i}s...");
try
{
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
catch (OperationCanceledException)
{
Console.WriteLine($"\n[{DateTime.Now:HH:mm:ss}] Polling cancelled.");
return false;
}
}
}
return false;
}
}
}
@@ -1,55 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Data.SqlClient;
using System.Configuration;
namespace bnhtrade.Core.Data.Database.UnitOfWork
{
public class Connection
{
//protected readonly string SqlConnectionString;
private Model.Credentials.bnhtradeDB _dbCredentials;
protected string SqlConnectionString
{
get { return _dbCredentials.ConnectionString; }
}
public Connection()
{
var config = new Config().GetConfiguration();
// attempt to retrive credentials from app.local.config
try
{
string dataSource = config.AppSettings.Settings["DbDataSource"].Value;
string userId = config.AppSettings.Settings["DbUserId"].Value;
string pass = config.AppSettings.Settings["DbUserPassword"].Value;
// check
if (string.IsNullOrEmpty(dataSource))
{
throw new ArgumentException("Could not retrive 'DbDataSource' from config file");
}
else if (string.IsNullOrEmpty(userId))
{
throw new ArgumentException("Could not retrive 'DbUserId' from config file");
}
else if (string.IsNullOrEmpty(pass))
{
throw new ArgumentException("Could not retrive 'DbUserPassword' from config file");
}
var dbCredentials = new bnhtrade.Core.Model.Credentials.bnhtradeDB(dataSource, userId, pass);
this._dbCredentials = dbCredentials;
}
catch (Exception ex)
{
throw new Exception("Unable to retirve DB credentials: " + ex.Message);
}
}
}
}
@@ -9,7 +9,7 @@ using System.Transactions;
namespace bnhtrade.Core.Logic.Stock
{
public class SkuTransactionTypeCrud : Connection // this inheritance can be removed when old code is removed below
public class SkuTransactionTypeCrud
{
private Data.Database.Stock.ReadSkuTransactionType dbRead;
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace bnhtrade.Core.Logic.Utilities
@@ -14,8 +15,23 @@ namespace bnhtrade.Core.Logic.Utilities
{
}
public async Task<bool> IsServerOnlineAsync()
{
using var cts = new CancellationTokenSource();
Console.CancelKeyPress += (sender, e) => { e.Cancel = true; cts.Cancel(); };
return await new bnhtrade.Core.Logic.Utilities.SqlServerPing()
.WaitForServerAsync(timeout: 15, maxAttempts: 60, cts.Token);
}
public void DownloadAll()
{
if (!IsServerOnlineAsync().GetAwaiter().GetResult())
{
Console.WriteLine("Server is not online, skipping nightly scheduled tasks.");
return;
}
log.LogInformation("Nightly scheduled tasks started.");
bool stockUpdate = false;
@@ -0,0 +1,25 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace bnhtrade.Core.Logic.Utilities
{
/// <summary>
/// Polls SQL Server until it comes online, max attempts are reached, or the operation is cancelled.
/// Intended for use on application startup when the remote SQL Server machine may still be booting.
/// </summary>
public class SqlServerPing
{
/// <summary>
/// Polls SQL Server until it comes online, max attempts are reached, or the cancellation token is triggered.
/// </summary>
/// <param name="timeout">Seconds each connection attempt waits before failing (1300).</param>
/// <param name="maxAttempts">Maximum number of poll attempts. 0 = retry indefinitely.</param>
/// <param name="cancellationToken">Token to cancel the polling loop.</param>
/// <returns>True when the server responds, false if max attempts reached or cancelled.</returns>
public async Task<bool> WaitForServerAsync(uint timeout = 10, uint maxAttempts = 6, CancellationToken cancellationToken = default)
{
return await new Data.Database.ServerPing().WaitForServerAsync(timeout, maxAttempts, cancellationToken);
}
}
}
@@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bnhtrade.Core.Model.Credentials
{
public class AmazonSPAPI
{
public string AccessKey { get; private set; }
public string SecretKey { get; private set; }
public string RoleArn { get; private set; }
public string ClientId { get; private set; }
public string ClientSecret { get; private set; }
public string RefreshToken { get; private set; }
public AmazonSPAPI(string accessKey, string secretKey, string roleArn, string clientId, string clientSecret, string refreshToken)
{
this.AccessKey = accessKey;
this.SecretKey = secretKey;
this.RoleArn = roleArn;
this.ClientId = clientId;
this.ClientSecret = clientSecret;
this.RefreshToken = refreshToken;
}
}
}
@@ -1,43 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bnhtrade.Core.Model.Credentials
{
public class bnhtradeDB
{
public string DataSource { get; private set; }
public string UserId { get; private set; }
public string UserPassword { get; private set; }
public string InitialCatalog { get; private set; } = "e2A";
public bool PersistSecurityInfo { get; private set; } = true;
public bool MultipleActiveResultSets { get; private set; } = true;
public uint ConnectionTimeout { get; private set; }
public string ConnectionString
{
get
{
return "Data Source=" + DataSource + ";Initial Catalog=" + InitialCatalog + ";Persist Security Info=" + PersistSecurityInfo.ToString()
+ ";User ID=" + UserId + ";Password=" + UserPassword + ";MultipleActiveResultSets=" + MultipleActiveResultSets.ToString()
+ ";Connect Timeout=" + ConnectionTimeout + ";Encrypt=True";
}
}
public bnhtradeDB (string source, string userId, string userPassword, uint connectionTimeout = 30)
{
this.DataSource = source;
this.UserId = userId;
this.UserPassword = userPassword;
this.ConnectionTimeout = connectionTimeout;
}
}
}
+13 -3
View File
@@ -2,6 +2,7 @@
using System;
using System.Configuration;
using System.Threading;
using System.Threading.Tasks;
using System.Transactions;
namespace bnhtradeScheduledTasks
@@ -12,7 +13,7 @@ namespace bnhtradeScheduledTasks
{
}
static void Main(string[] args)
static async Task Main(string[] args)
{
if (OperatingSystem.IsWindows())
{
@@ -310,7 +311,7 @@ namespace bnhtradeScheduledTasks
Console.WriteLine(consoleHeader);
Console.WriteLine("Main Menu > Dev Funcions");
Console.WriteLine();
Console.WriteLine("<1> Test some randon function I've set here");
Console.WriteLine("<1> Ping SQL Server");
Console.WriteLine("<2> Test Account");
Console.WriteLine("<3> Test Export");
Console.WriteLine("<4> Test Import");
@@ -334,7 +335,16 @@ namespace bnhtradeScheduledTasks
{
Console.Clear();
var obj = new bnhtrade.Core.Test.Amazon.SP_API.VariousCalls();
using var cts = new CancellationTokenSource();
Console.CancelKeyPress += (sender, e) => { e.Cancel = true; cts.Cancel(); };
bool online = await new bnhtrade.Core.Logic.Utilities.SqlServerPing()
.WaitForServerAsync(timeout: 10, maxAttempts: 10, cts.Token);
if (!online)
{
Console.WriteLine("Could not connect to SQL Server.");
}
Console.WriteLine("Complete, press any key to continue...");
Console.ReadKey();