2 Commits

Author SHA1 Message Date
bobbie e6c784fccf Merge branch 'master' into Amazon-inventory-ledger-testing-and-implementation
sync master with branch
2026-04-30 10:30:44 +01:00
Bobbie Hodgetts fccf2190cd Merge pull request #48 from stokebob/master
sync with master
2026-04-30 09:50:21 +01:00
10 changed files with 195 additions and 188 deletions
+60 -46
View File
@@ -1,78 +1,92 @@
using FikaAmazonAPI.AmazonSpApiSDK.Models.Services; using System;
using System;
using System.Configuration; using System.Configuration;
namespace bnhtrade.Core.Data.Database 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 public class Connection
{ {
private string _server; //protected readonly string SqlConnectionString;
private string _user; private Model.Credentials.bnhtradeDB _dbCredentials;
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;
internal string SqlConnectionString { get; private set; } protected string SqlConnectionString
/// <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) get { return _dbCredentials.ConnectionString; }
{ }
// 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 public Connection()
{
var config = new Config().GetConfiguration(); var config = new Config().GetConfiguration();
// attempt to retrive credentials from app.local.config
try try
{ {
_server = config.AppSettings.Settings["DbDataSource"].Value; string dataSource = config.AppSettings.Settings["DbDataSource"].Value;
_user = config.AppSettings.Settings["DbUserId"].Value; string userId = config.AppSettings.Settings["DbUserId"].Value;
_userPassword = config.AppSettings.Settings["DbUserPassword"].Value; string pass = config.AppSettings.Settings["DbUserPassword"].Value;
// check // check
if (string.IsNullOrEmpty(_server)) if (string.IsNullOrEmpty(dataSource))
{ {
throw new ArgumentException("Could not retrive 'DbDataSource' from config file"); throw new ArgumentException("Could not retrive 'DbDataSource' from config file");
} }
else if (string.IsNullOrEmpty(_user)) else if (string.IsNullOrEmpty(userId))
{ {
throw new ArgumentException("Could not retrive 'DbUserId' from config file"); throw new ArgumentException("Could not retrive 'DbUserId' from config file");
} }
else if (string.IsNullOrEmpty(_userPassword)) else if (string.IsNullOrEmpty(pass))
{ {
throw new ArgumentException("Could not retrive 'DbUserPassword' from config file"); 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) catch (Exception ex)
{ {
throw new Exception("Unable to retirve DB credentials: " + ex.Message); throw new Exception("Unable to retirve DB credentials: " + ex.Message);
} }
}
// build connection string private void ConnectionOld()
SqlConnectionString = "Server=" + _server + ";Database=" + _database + ";PersistSecurityInfo=" + _persistSecurityInfo.ToString() {
+ ";User=" + _user + ";Password=" + _userPassword + ";MultipleActiveResultSets=" + _multipleActiveResultSets.ToString() // attempt to retrive credentials from app.local.config
+ ";ConnectRetryInterval=" + _connectRetryInterval + ";ConnectRetryCount=" + _connectRetryCount + ";Timeout=" + _connectTimeout try
+ ";Encrypt=" + _encrypt.ToString() +";"; {
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;
} }
} }
} }
@@ -10,6 +10,11 @@ namespace bnhtrade.Core.Data.Database.Sku.Price
{ {
public class ReadParameter : Connection public class ReadParameter : Connection
{ {
public ReadParameter() : base()
{
}
public List<Model.Sku.Price.SkuRepriceInfo> Execute() public List<Model.Sku.Price.SkuRepriceInfo> Execute()
{ {
string stringSql = @" string stringSql = @"
@@ -1,87 +0,0 @@
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;
}
}
}
@@ -0,0 +1,55 @@
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 namespace bnhtrade.Core.Logic.Stock
{ {
public class SkuTransactionTypeCrud public class SkuTransactionTypeCrud : Connection // this inheritance can be removed when old code is removed below
{ {
private Data.Database.Stock.ReadSkuTransactionType dbRead; private Data.Database.Stock.ReadSkuTransactionType dbRead;
@@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace bnhtrade.Core.Logic.Utilities namespace bnhtrade.Core.Logic.Utilities
@@ -15,23 +14,8 @@ 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() public void DownloadAll()
{ {
if (!IsServerOnlineAsync().GetAwaiter().GetResult())
{
Console.WriteLine("Server is not online, skipping nightly scheduled tasks.");
return;
}
log.LogInformation("Nightly scheduled tasks started."); log.LogInformation("Nightly scheduled tasks started.");
bool stockUpdate = false; bool stockUpdate = false;
@@ -1,25 +0,0 @@
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);
}
}
}
@@ -0,0 +1,28 @@
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;
}
}
}
@@ -0,0 +1,43 @@
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;
}
}
}
+3 -13
View File
@@ -2,7 +2,6 @@
using System; using System;
using System.Configuration; using System.Configuration;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using System.Transactions; using System.Transactions;
namespace bnhtradeScheduledTasks namespace bnhtradeScheduledTasks
@@ -13,7 +12,7 @@ namespace bnhtradeScheduledTasks
{ {
} }
static async Task Main(string[] args) static void Main(string[] args)
{ {
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
{ {
@@ -311,7 +310,7 @@ namespace bnhtradeScheduledTasks
Console.WriteLine(consoleHeader); Console.WriteLine(consoleHeader);
Console.WriteLine("Main Menu > Dev Funcions"); Console.WriteLine("Main Menu > Dev Funcions");
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("<1> Ping SQL Server"); Console.WriteLine("<1> Test some randon function I've set here");
Console.WriteLine("<2> Test Account"); Console.WriteLine("<2> Test Account");
Console.WriteLine("<3> Test Export"); Console.WriteLine("<3> Test Export");
Console.WriteLine("<4> Test Import"); Console.WriteLine("<4> Test Import");
@@ -335,16 +334,7 @@ namespace bnhtradeScheduledTasks
{ {
Console.Clear(); Console.Clear();
using var cts = new CancellationTokenSource(); var obj = new bnhtrade.Core.Test.Amazon.SP_API.VariousCalls();
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.WriteLine("Complete, press any key to continue...");
Console.ReadKey(); Console.ReadKey();