1.2.11.4
This commit is contained in:
@@ -100,6 +100,7 @@ namespace Saxonite.Net.Core.Web.Base.App
|
||||
{
|
||||
public Dictionary<Sax.Database, DatabaseConfig> Databases { get; } = new Dictionary<Sax.Database, DatabaseConfig>();
|
||||
public DatabaseConfig Core { get { return Databases.GetValue(Sax.Database.Core, new DatabaseConfig()); } }
|
||||
public DatabaseConfig Reference { get { return Databases.GetValue(Sax.Database.Reference, new DatabaseConfig()); } }
|
||||
public DatabaseConfig Support { get { return Databases.GetValue(Sax.Database.Support, new DatabaseConfig()); } }
|
||||
}
|
||||
public DatabasesConfig Databases { get; } = new DatabasesConfig();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Dbas;
|
||||
@@ -10,8 +12,10 @@ using Sarand.Net.Standard.Dbas.Data;
|
||||
using Saxonite.Net.Core.Web.Base.Consoles;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Consoles;
|
||||
using Saxonite.Net.Standard.Base.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
@@ -34,15 +38,17 @@ namespace Saxonite.Net.Core.Web.Base.App
|
||||
public SaxSessionContext_Web SessionContext { get; }
|
||||
public Dictionary<Sax.Database, ISacsDatabase> Databases { get; } = new Dictionary<Sax.Database, ISacsDatabase>();
|
||||
public ISacsDatabase CoreDatabase { get { return Databases.GetValue(Sax.Database.Core, null); } }
|
||||
public ISacsDatabase ReferenceDatabase { get { return Databases.GetValue(Sax.Database.Reference, null); } }
|
||||
public ISacsDatabase SupportDatabase { get { return Databases.GetValue(Sax.Database.Support, null); } }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Protected construction.
|
||||
protected SaxApplication_Web()
|
||||
protected SaxApplication_Web(Sax.Application applicationType)
|
||||
{
|
||||
WebApp = this;
|
||||
System_Web = new SaxSystem_Web();
|
||||
ApplicationType = applicationType;
|
||||
}
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
@@ -57,6 +63,7 @@ namespace Saxonite.Net.Core.Web.Base.App
|
||||
//-------------------------------------------------------------------
|
||||
// Globals.
|
||||
public static SaxApplication_Web WebApp { get; private set; }
|
||||
public Sax.Application ApplicationType { get; }
|
||||
public override SaxSystem System { get { return System_Web; } }
|
||||
public SaxSystem_Web System_Web { get; private set; }
|
||||
// Application instance.
|
||||
@@ -72,7 +79,51 @@ namespace Saxonite.Net.Core.Web.Base.App
|
||||
public override IDatabaseInfo CreateDbInfo(SaxSessionContext sessionContext, Sax.Database databaseType) { return new DbDataItem.DatabaseInfo(GetDatabase(sessionContext, databaseType)); }
|
||||
// Configuration.
|
||||
public IConfiguration Configuration { get; protected set; }
|
||||
public IConfiguration SaxConfiguration { get; protected set; }
|
||||
public IConfiguration SaxConfiguration { get; private set; }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
protected void InitEnvironment(IWebHostEnvironment env, String logFilenamePrefix)
|
||||
{
|
||||
Development = env.IsDevelopment();
|
||||
environmentName = env.EnvironmentName;
|
||||
SaxConfiguration = LoadApplicationConfiguration();
|
||||
|
||||
// Configure logging.
|
||||
SaLogger.LogFilenamePrefix = logFilenamePrefix;
|
||||
SaLogger.IncludeHostInFileMessage = false;
|
||||
SaLogger.IncludeUserInFileMessage = false;
|
||||
|
||||
//Messages.Add($"Host: {SaxSystem.ComputerName}");
|
||||
//Messages.Add($"Config file: appsettings.machine.{SaxSystem.ComputerName}.json");
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Application configuration.
|
||||
private IConfiguration LoadApplicationConfiguration()
|
||||
{
|
||||
// Load configuration resource files.
|
||||
// Later files override values from previous.
|
||||
var builder =
|
||||
new ConfigurationBuilder()
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddEnvironmentVariables();
|
||||
|
||||
// Saxonite Web configuration - shared by all applications.
|
||||
Stream configStream = SaxResourceHelper.GetResourceStream("appsettings.json", "Config");
|
||||
if (configStream != null) builder.AddJsonStream(configStream);
|
||||
configStream = SaxResourceHelper.GetResourceStream($"appsettings.{environmentName}.json", "Config");
|
||||
if (configStream != null) builder.AddJsonStream(configStream);
|
||||
configStream = SaxResourceHelper.GetResourceStream($"appsettings.machine.{SaxSystem.ComputerName}.json", "Config");
|
||||
if (configStream != null) builder.AddJsonStream(configStream);
|
||||
|
||||
// Application specific configuration.
|
||||
builder
|
||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
|
||||
.AddJsonFile($"appsettings.{environmentName}.json", optional: true, reloadOnChange: false)
|
||||
.AddJsonFile($"appsettings.machine.{SaxSystem.ComputerName}.json", optional: true, reloadOnChange: false);
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Database configuration.
|
||||
@@ -103,13 +154,15 @@ namespace Saxonite.Net.Core.Web.Base.App
|
||||
applicationParams.Add("Environment", EnvironmentName);
|
||||
if (!StringUtil.IsBlank(AppConfig.Databases.Core.Location))
|
||||
applicationParams.Add("Database - Core", AppConfig.Databases.Core.Location);
|
||||
if (!StringUtil.IsBlank(AppConfig.Databases.Reference.Location))
|
||||
applicationParams.Add("Database - Reference", AppConfig.Databases.Reference.Location);
|
||||
if (!StringUtil.IsBlank(AppConfig.Databases.Support.Location))
|
||||
applicationParams.Add("Database - Support", AppConfig.Databases.Support.Location);
|
||||
applicationParams.Add("Application root", SaxSystem.ApplicationFolder);
|
||||
if (!StringUtil.IsEmpty(AppConfig.Documents.RootFolder))
|
||||
applicationParams.Add("Documents root", AppConfig.Documents.RootFolder);
|
||||
applicationParams.Add("Email context", AppConfig.Email.Context + (!StringUtil.IsEmpty(EmailConfig.SMTP.Server) ? " [" + EmailConfig.SMTP.Server + "]" : ""));
|
||||
SaLogger.Info(fn + " Application start." + applicationParams.Select(item => StringUtil.PadRight(item.Item1 + ":", 20, ' ') + item.Item2).Concat("", "\n\t"), true);
|
||||
SaLogger.Info(fn + " Application start." + applicationParams.Select(item => StringUtil.PadRight(item.Item1 + ":", 23, ' ') + " " + item.Item2).Concat("", "\n\t"), true);
|
||||
if (StringUtil.IsEmpty(EmailConfig.SMTP.Server) || StringUtil.IsEmpty(EmailConfig.Message.AddressFrom))
|
||||
SaLogger.Warn(fn + " Incomplete email configuration.", true);
|
||||
}
|
||||
|
||||
@@ -63,12 +63,13 @@ namespace Saxonite.Net.Core.Web.Base.App
|
||||
// Authentication service.
|
||||
// Required to handle 'Forbidden' responses etc.
|
||||
config.services
|
||||
.AddAuthentication(options => {
|
||||
.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = "forbidScheme";
|
||||
options.DefaultForbidScheme = "forbidScheme";
|
||||
options.AddScheme<SaxAuthenticationHandler>("forbidScheme", "Handle Forbidden");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Added to support memory caching of sessions.
|
||||
config.services.AddMemoryCache();
|
||||
//config.services.AddDistributedMemoryCache(); - for multiple web servers.
|
||||
@@ -91,11 +92,14 @@ namespace Saxonite.Net.Core.Web.Base.App
|
||||
// Modify pages during debug.
|
||||
#if DEBUG
|
||||
if (SaxSystem.DebugMode)
|
||||
{
|
||||
// If the following line throws an error such as:
|
||||
// 'imvcbuilder' does not contain a definition for 'addrazorruntimecompilation'
|
||||
// Revert the NuGet Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation package
|
||||
// to a previous version and then reinstal the latest version.
|
||||
config.services.AddRazorPages().AddRazorRuntimeCompilation();
|
||||
//config.services.AddControllersWithViews().AddRazorRuntimeCompilation();
|
||||
}
|
||||
#endif
|
||||
|
||||
config.services.AddMvc
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Tasks;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Consoles
|
||||
{
|
||||
//=======================================================================
|
||||
public abstract class SaxHostedServiceConsole_Web : SacsDisposable, IHostedService
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private Timer processTimer;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
protected SaxHostedServiceConsole_Web ()
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Clean-up.
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
DisposeTimer();
|
||||
if (ProcessTaskData != null) ProcessTaskData.Dispose();
|
||||
ProcessTaskData = null;
|
||||
}
|
||||
|
||||
private void DisposeTimer()
|
||||
{
|
||||
if (processTimer != null) processTimer.Dispose();
|
||||
processTimer = null;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
|
||||
// Task data - allows cancellation to be triggered for executing processes, eg. when application is stopping.
|
||||
protected SacsTaskData ProcessTaskData { get; private set; }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Periodic processing to execute application processes as required.
|
||||
/// </summary>
|
||||
protected abstract void DoWork(object state);
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when the application host is ready to start the service.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
|
||||
/// <returns></returns>
|
||||
public virtual Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
SaLogger.Info($"{SysUtil.Fn} Timed Background Service started.");
|
||||
|
||||
// Initialise the background task cancellation token.
|
||||
ProcessTaskData = new SacsTaskData();
|
||||
|
||||
// Initialise the scheduled task timer.
|
||||
processTimer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when the application host is performing a graceful shutdown.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
|
||||
/// <returns></returns>
|
||||
public virtual Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
SaLogger.Info($"{SysUtil.Fn} Timed Background Service stopped.");
|
||||
|
||||
// Signal cancellation to the background tasks.
|
||||
ProcessTaskData.TriggerCancellation();
|
||||
|
||||
// Stop the timer.
|
||||
processTimer?.Change(Timeout.Infinite, 0);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -21,6 +21,7 @@ using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Bus;
|
||||
using Saxonite.Net.Standard.Base.Consoles;
|
||||
using Saxonite.Net.Standard.Base.Mgr;
|
||||
using Saxonite.Net.Standard.Base.Mgr.Reference;
|
||||
using Saxonite.Net.Standard.Base.Mgr.Support;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -83,6 +84,7 @@ namespace Saxonite.Net.Core.Web.Base.Controllers
|
||||
public SaxAccountMgr AccountMgr { get { return Internals.AccountMgr; } }
|
||||
public SaxActivityMgr ActivityMgr { get { return Internals.ActivityMgr; } }
|
||||
public SaxItemDataMgr ItemDataMgr { get { return Internals.ItemDataMgr; } }
|
||||
public SaxReferenceGolfMgr ReferenceGolfMgr { get { return Internals.ReferenceGolfMgr; } }
|
||||
public SaxSupportMgr SupportMgr { get { return Internals.SupportMgr; } }
|
||||
// System codes.
|
||||
public List<TypeCodeDesc> DataItemTypeCodes { get { return Internals.DataItemTypeCodes; } }
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using static Saxonite.Net.Standard.Base.App.Sax.Config.Security;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Middleware
|
||||
{
|
||||
@@ -57,10 +58,12 @@ namespace Saxonite.Net.Core.Web.Base.Middleware
|
||||
{
|
||||
//if (SaxSystem.DebugMode) Logger.Debug(fn + " Form property " + modelName + ": '" + result.Model.ToString() + ".");
|
||||
object originalValue = result.Model;
|
||||
// Exclude properties that retain leading/trailing whitespace.
|
||||
if (result.Model is string)
|
||||
// Anything that looks like a password will be left as is.
|
||||
bool password = modelName.ContainsCI("Password");
|
||||
// Exclude properties that retain leading/trailing whitespace.
|
||||
if (result.Model is string)
|
||||
{
|
||||
if (!modelName.ContainsCI("Password"))
|
||||
if (!password)
|
||||
{
|
||||
string value = result.Model as string;
|
||||
|
||||
@@ -92,10 +95,12 @@ namespace Saxonite.Net.Core.Web.Base.Middleware
|
||||
}
|
||||
// Log property details.
|
||||
if (result.Model != null && SaxSystem.DebugMode &&
|
||||
// Ignore items that correspond with objects in the model rather than values.
|
||||
!result.Model.ToString().Equals(result.Model.GetType().FullName) &&
|
||||
// Ignore form files.
|
||||
!result.Model.ToString().ContainsCI("Microsoft.AspNetCore.Http.IFormFile"))
|
||||
// Ignore passwords.
|
||||
!password &&
|
||||
// Ignore items that correspond with objects in the model rather than values.
|
||||
!result.Model.ToString().Equals(result.Model.GetType().FullName) &&
|
||||
// Ignore form files.
|
||||
!result.Model.ToString().ContainsCI("Microsoft.AspNetCore.Http.IFormFile"))
|
||||
SaLogger.Debug(SysUtil.Fn + " Form property " + modelName + ": '" + result.Model.ToString() + "'." + (originalValue != null && !originalValue.Equals(result.Model) ? " Original value: '" + originalValue.ToString() + "'" : ""));
|
||||
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Bus;
|
||||
using Saxonite.Net.Standard.Base.Consoles;
|
||||
using Saxonite.Net.Standard.Base.Mgr;
|
||||
using Saxonite.Net.Standard.Base.Mgr.Reference;
|
||||
using Saxonite.Net.Standard.Base.Mgr.Support;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -67,6 +68,7 @@ namespace Saxonite.Net.Core.Web.Base.Models
|
||||
public SaxAccountMgr AccountMgr { get { return Internals.AccountMgr; } }
|
||||
public SaxActivityMgr ActivityMgr { get { return Internals.ActivityMgr; } }
|
||||
public SaxItemDataMgr ItemDataMgr { get { return Internals.ItemDataMgr; } }
|
||||
public SaxReferenceGolfMgr ReferenceGolfMgr { get { return Internals.ReferenceGolfMgr; } }
|
||||
public SaxSupportMgr SupportMgr { get { return Internals.SupportMgr; } }
|
||||
// System codes.
|
||||
public List<TypeCodeDesc> DataItemTypeCodes { get { return Internals.DataItemTypeCodes; } }
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
using DocumentFormat.OpenXml.Drawing.Diagrams;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Dbas;
|
||||
using Sarand.Net.Standard.Base.Helpers;
|
||||
using Sarand.Net.Standard.Base.Tasks;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Bus.Reference;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Procs
|
||||
{
|
||||
//=======================================================================
|
||||
partial class SaxGolfUsgaDataLookup
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
private class ScheduledUsgaDataLookupControlData
|
||||
{
|
||||
public DateTime CountriesLastLookedUp { get; set; } = DateTime.Now;
|
||||
}
|
||||
private static ScheduledUsgaDataLookupControlData usgaDataLookupControl { get; } = new ScheduledUsgaDataLookupControlData();
|
||||
|
||||
/// <summary>
|
||||
/// Determine which USGA data population process to run.
|
||||
/// <para>- Method is invoked periodically (every 20 mins) by the Admin Hosted Service Console.</para>
|
||||
/// <para>- 1. Populate countries if not already done or not done for a while (eg. 24 hours).</para>
|
||||
/// <para>- 2. Populate provinces for a Country that has none or occasionally to refresh one that has.</para>
|
||||
/// <para>- 3. Populate Courses for a Province that has none or occasionally to refresh one that has.</para>
|
||||
/// <para>- 4. Populate Courses for a Province that has none or occasionally to refresh one that has.</para>
|
||||
/// </summary>
|
||||
public static void ExecuteScheduledUsgaDataLookup(SaxSessionContext sessionContext, DbUpdateInfo dbUpdateInfo, SacsTaskData taskData)
|
||||
{
|
||||
SaLogger.Debug($"{SysUtil.Fn}");
|
||||
|
||||
using (DisposableCollection<SacsDisposable> disposableItems = new DisposableCollection<SacsDisposable>())
|
||||
{
|
||||
SaxInternals internals = new SaxInternals(sessionContext);
|
||||
disposableItems.Add(internals);
|
||||
|
||||
// Configure process.
|
||||
SaxGolfUsgaDataLookup.Config courseLookupConfig = new SaxGolfUsgaDataLookup.Config(sessionContext, dbUpdateInfo);
|
||||
SaxGolfUsgaDataLookup usgaDataLookup = new SaxGolfUsgaDataLookup(courseLookupConfig);
|
||||
|
||||
if (taskData.IsCancellationRequested) return;
|
||||
|
||||
// Determine which process to run.
|
||||
// Check USGA country population.
|
||||
List<SaxGolfUsgaCountry> usgaCountries = internals.ReferenceGolfMgr.GetUsgaCountryList(new SaxGolfUsgaCountry.Params());
|
||||
if (!usgaCountries.Any() || (DateTime.Now - usgaDataLookupControl.CountriesLastLookedUp).TotalHours > MathUtil.Constrain(Sax.Config.Admin.Processes.Scheduled.UsgaGolfDataLookup.HoursBetweenCountryLookup, 1, 24 * 7))
|
||||
{
|
||||
// No countries currently found or long enough since last lookup.
|
||||
// Lookup USGA countries.
|
||||
List<SaxGolfUsgaDataLookup.CountryData> countriesData = usgaDataLookup.LookupUsgaCountries();
|
||||
SaLogger.Info($"{SysUtil.Fn} USGA Countries populated: {countriesData.Count}.", true);
|
||||
usgaDataLookupControl.CountriesLastLookedUp = DateTime.Now;
|
||||
return;
|
||||
}
|
||||
if (taskData.IsCancellationRequested) return;
|
||||
|
||||
// Population of Countries without Provinces is only allowed occasionally to avoid delaying population of lower data.
|
||||
bool populateCountryWithNoProvinces = SacsRandomHelper.GetInt32(1, 7) == 1;
|
||||
|
||||
// Check USGA Country Province population.
|
||||
List<SaxGolfUsgaCountry> usgaCountriesWithProvinces = new List<SaxGolfUsgaCountry>();
|
||||
List<SaxGolfUsgaCountry> usgaCountriesWithNoProvinces = new List<SaxGolfUsgaCountry>();
|
||||
foreach (SaxGolfUsgaCountry usgaCountry in usgaCountries.OrderBy(_ => SacsRandomHelper.GetInt32(Int32.MaxValue)))
|
||||
{
|
||||
SaxGolfUsgaProvince.Params prmUsgaProvince = new SaxGolfUsgaProvince.Params();
|
||||
prmUsgaProvince.CountryIdsIncluded.Add(usgaCountry.Id);
|
||||
if (internals.ReferenceGolfMgr.GetUsgaProvinceCount(prmUsgaProvince) < 1)
|
||||
{
|
||||
usgaCountriesWithNoProvinces.Add(usgaCountry);
|
||||
if (populateCountryWithNoProvinces)
|
||||
{
|
||||
// Country has no Provinces - look them up.
|
||||
List<SaxGolfUsgaDataLookup.ProvinceData> provincesData = usgaDataLookup.LookupUsgaProvincesForCountry(usgaCountry.CountryCode);
|
||||
SaLogger.Info($"{SysUtil.Fn} USGA Provinces populated for {usgaCountry.CountryCode} {usgaCountry.CountryName}: {provincesData.Count}.", true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Country has Provinces.
|
||||
usgaCountriesWithProvinces.Add(usgaCountry);
|
||||
}
|
||||
if (taskData.IsCancellationRequested) return;
|
||||
}
|
||||
|
||||
if (usgaCountriesWithProvinces.Any())
|
||||
{
|
||||
// Pick the first (randomly sorted) Country.
|
||||
SaxGolfUsgaCountry usgaCountry = usgaCountriesWithProvinces[0];
|
||||
|
||||
// One of the Countries with Provinces can have its list of provinces refreshed occasionally.
|
||||
bool refreshCountryWithProvinces = SacsRandomHelper.GetInt32(1, 17) == 1;
|
||||
if (refreshCountryWithProvinces)
|
||||
{
|
||||
SaxGolfUsgaProvince.Params prmUsgaProvince = new SaxGolfUsgaProvince.Params();
|
||||
prmUsgaProvince.CountryIdsIncluded.Add(usgaCountry.Id);
|
||||
List<SaxGolfUsgaProvince> usgaProvinces = internals.ReferenceGolfMgr.GetUsgaProvinceList(prmUsgaProvince).OrderBy(_ => SacsRandomHelper.GetInt32(Int32.MaxValue)).ToList();
|
||||
if (usgaProvinces.Any())
|
||||
{
|
||||
// Refresh Country Provinces.
|
||||
List<SaxGolfUsgaDataLookup.ProvinceData> provincesData = usgaDataLookup.LookupUsgaProvincesForCountry(usgaCountry.CountryCode);
|
||||
SaLogger.Info($"{SysUtil.Fn} USGA Provinces refreshed for {usgaCountry.CountryCode} {usgaCountry.CountryName}: {provincesData.Count}.", true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (usgaCountriesWithProvinces.Any())
|
||||
{
|
||||
// Pick the first (randomly sorted) Country.
|
||||
SaxGolfUsgaCountry usgaCountry = usgaCountriesWithProvinces[0];
|
||||
|
||||
// Population of Courses for Province without Facilities is allowed occasionally.
|
||||
bool populateProvinceWithNoFacilities = SacsRandomHelper.GetInt32(1, 7) == 1;
|
||||
|
||||
// Check USGA Province Course population.
|
||||
List<SaxGolfUsgaProvince> usgaProvincesWithFacilities = new List<SaxGolfUsgaProvince>();
|
||||
List<SaxGolfUsgaProvince> usgaProvincesWithNoFacilities = new List<SaxGolfUsgaProvince>();
|
||||
SaxGolfUsgaProvince.Params prmUsgaProvince = new SaxGolfUsgaProvince.Params();
|
||||
prmUsgaProvince.CountryIdsIncluded.Add(usgaCountry.Id);
|
||||
foreach (SaxGolfUsgaProvince usgaProvince in internals.ReferenceGolfMgr.GetUsgaProvinceList(prmUsgaProvince).OrderBy(_ => SacsRandomHelper.GetInt32(Int32.MaxValue)))
|
||||
{
|
||||
SaxGolfUsgaFacility.Params prmUsgaFacility = new SaxGolfUsgaFacility.Params();
|
||||
prmUsgaFacility.ProvinceIdsIncluded.Add(usgaProvince.Id);
|
||||
if (internals.ReferenceGolfMgr.GetUsgaFacilityCount(prmUsgaFacility) < 1)
|
||||
{
|
||||
usgaProvincesWithNoFacilities.Add(usgaProvince);
|
||||
if (populateProvinceWithNoFacilities)
|
||||
{
|
||||
// Province has no Facilities - look up the courses.
|
||||
List<SaxGolfUsgaDataLookup.CourseData> coursesData = usgaDataLookup.LookupUsgaCoursesForCountryAndProvince(usgaCountry.CountryCode, usgaProvince.ProvinceCode);
|
||||
SaLogger.Info($"{SysUtil.Fn} USGA Courses populated for {usgaCountry.CountryCode} {usgaCountry.CountryName} - {usgaProvince.ProvinceCode} {usgaProvince.ProvinceName}: {coursesData.Count}.", true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Country has provinces.
|
||||
usgaProvincesWithFacilities.Add(usgaProvince);
|
||||
}
|
||||
if (taskData.IsCancellationRequested) return;
|
||||
}
|
||||
|
||||
if (usgaProvincesWithFacilities.Any())
|
||||
{
|
||||
// Pick the first (randomly sorted) province.
|
||||
SaxGolfUsgaProvince usgaProvince = usgaProvincesWithFacilities[0];
|
||||
|
||||
// Population of Courses for Province with Facilities is allowed occasionally.
|
||||
bool populateProvinceWithFacilities = SacsRandomHelper.GetInt32(1, 17) == 1;
|
||||
if (populateProvinceWithFacilities)
|
||||
{
|
||||
// Refresh Courses for Province with Facilities.
|
||||
List<SaxGolfUsgaDataLookup.CourseData> coursesData = usgaDataLookup.LookupUsgaCoursesForCountryAndProvince(usgaCountry.CountryCode, usgaProvince.ProvinceCode);
|
||||
SaLogger.Info($"{SysUtil.Fn} USGA Courses refreshed for {usgaCountry.CountryCode} {usgaCountry.CountryName} - {usgaProvince.ProvinceCode} {usgaProvince.ProvinceName}: {coursesData.Count}.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Compile list of Courses for this province.
|
||||
SaxGolfUsgaCourse.Params prmCourse = new SaxGolfUsgaCourse.Params();
|
||||
prmCourse.ProvinceIdsIncluded.Add(usgaProvince.Id);
|
||||
List<SaxGolfUsgaCourse> usgaCourses = internals.ReferenceGolfMgr.GetUsgaCourseList(prmCourse).OrderBy(_ => SacsRandomHelper.GetInt32(Int32.MaxValue)).ToList();
|
||||
List<SaxGolfUsgaCourse> usgaCoursesWithMarkers = new List<SaxGolfUsgaCourse>();
|
||||
foreach (SaxGolfUsgaCourse usgaCourse in usgaCourses)
|
||||
{
|
||||
SaxGolfUsgaMarker.Params prmMarker = new SaxGolfUsgaMarker.Params();
|
||||
prmMarker.CourseIdsIncluded.Add(usgaCourse.Id);
|
||||
if (internals.ReferenceGolfMgr.GetUsgaMarkerCount(prmMarker) == 0)
|
||||
{
|
||||
// Populate Markers for first course without markers.
|
||||
List<SaxGolfUsgaDataLookup.MarkerData> markersData = usgaDataLookup.LookupUsgaMarkersForCourse(usgaCourse.CourseId);
|
||||
SaLogger.Info($"{SysUtil.Fn} USGA Markers populated for {usgaCountry.CountryCode} {usgaCountry.CountryName} - {usgaProvince.ProvinceCode} {usgaProvince.ProvinceName} - {usgaCourse.Caption} [Id: {usgaCourse.CourseId}]: {markersData.Count}.", true);
|
||||
return;
|
||||
}
|
||||
else usgaCoursesWithMarkers.Add(usgaCourse);
|
||||
}
|
||||
if (usgaCoursesWithMarkers.Any())
|
||||
{
|
||||
// Populate Markers for first course with markers.
|
||||
SaxGolfUsgaCourse usgaCourse = usgaCoursesWithMarkers[0];
|
||||
List<SaxGolfUsgaDataLookup.MarkerData> markersData = usgaDataLookup.LookupUsgaMarkersForCourse(usgaCourse.CourseId);
|
||||
SaLogger.Info($"{SysUtil.Fn} USGA Markers refreshed for {usgaCountry.CountryCode} {usgaCountry.CountryName} - {usgaProvince.ProvinceCode} {usgaProvince.ProvinceName} - {usgaCourse.Caption} [Id: {usgaCourse.CourseId}]: {markersData.Count}.", true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback - populate Courses for Province.
|
||||
bool allowFallbackCoursePopulation = true;
|
||||
if (allowFallbackCoursePopulation)
|
||||
{
|
||||
SaxGolfUsgaProvince usgaProvince = usgaProvincesWithNoFacilities.Any() ? usgaProvincesWithNoFacilities[0] : usgaProvincesWithFacilities.Any() ? usgaProvincesWithFacilities[0] : null;
|
||||
if (usgaCountry != null && usgaProvince != null && usgaCountry.Id.Equals(usgaProvince.CountryId))
|
||||
{
|
||||
List<SaxGolfUsgaDataLookup.CourseData> coursesData = usgaDataLookup.LookupUsgaCoursesForCountryAndProvince(usgaCountry.CountryCode, usgaProvince.ProvinceCode);
|
||||
SaLogger.Info($"{SysUtil.Fn} USGA Courses populated for {usgaCountry.CountryCode} {usgaCountry.CountryName} - {usgaProvince.ProvinceCode} {usgaProvince.ProvinceName}: {coursesData.Count}. [Fallback]", true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback - populate provinces for country.
|
||||
bool allowFallbackProvincePopulation = true;
|
||||
if (allowFallbackProvincePopulation)
|
||||
{
|
||||
SaxGolfUsgaCountry usgaCountry = usgaCountriesWithNoProvinces.Any() ? usgaCountriesWithNoProvinces[0] : usgaCountriesWithProvinces.Any() ? usgaCountriesWithProvinces[0] : null;
|
||||
if (usgaCountry != null)
|
||||
{
|
||||
List<SaxGolfUsgaDataLookup.ProvinceData> provincesData = usgaDataLookup.LookupUsgaProvincesForCountry(usgaCountry.CountryCode);
|
||||
SaLogger.Info($"{SysUtil.Fn} USGA Provinces populated for {usgaCountry.CountryCode} {usgaCountry.CountryName}: {provincesData.Count}. [Fallback]", true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,847 @@
|
||||
using DocumentFormat.OpenXml.InkML;
|
||||
using HtmlAgilityPack;
|
||||
using MailKit;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Bus;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Dbas;
|
||||
using Sarand.Net.Standard.Base.Helpers;
|
||||
using Sarand.Net.Standard.Base.Net;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Mgr.Reference;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using static Saxonite.Net.Core.Web.Base.Procs.SaxGolfUsgaDataLookup;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Procs
|
||||
{
|
||||
//======================================================================
|
||||
// USGA course rating & slope database: https://ncrdb.usga.org/
|
||||
// https://ncrdb.usga.org/courseTeeInfo.aspx?CourseID=14758
|
||||
// Ref. https://zylalabs.com/blog/enhance-your-game-with-a-free-golf-api
|
||||
public partial class SaxGolfUsgaDataLookup : SaxProcess_Web
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
public new class Config : SaxProcess_Web.Config
|
||||
{
|
||||
public Config(SaxSessionContext sessionContext, DbUpdateInfo dbUpdateInfo) : base(sessionContext, dbUpdateInfo)
|
||||
{ }
|
||||
}
|
||||
|
||||
public class CountryProvinceCodes
|
||||
{
|
||||
public CountryProvinceCodes(String countryCode, String provinceCode)
|
||||
{
|
||||
this.CountryCode = StringUtil.OnBlank(countryCode);
|
||||
this.ProvinceCode = StringUtil.OnBlank(provinceCode);
|
||||
}
|
||||
public string CountryCode { get; }
|
||||
public string ProvinceCode { get; }
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as CountryProvinceCodes);
|
||||
}
|
||||
public bool Equals(CountryProvinceCodes other)
|
||||
{
|
||||
return other != null && CountryCode == other.CountryCode && ProvinceCode == other.ProvinceCode;
|
||||
}
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(CountryCode, ProvinceCode);
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return $"CountryProvinceCodes({CountryCode}, {ProvinceCode})";
|
||||
}
|
||||
}
|
||||
|
||||
public class CountryData
|
||||
{
|
||||
public string Code { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class ProvinceData
|
||||
{
|
||||
public string Code { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class CourseData
|
||||
{
|
||||
public string CourseId { get; set; }
|
||||
public string LegacyCrpCourseId { get; set; }
|
||||
public string Caption { get; set; }
|
||||
public string CourseName { get; set; }
|
||||
public string FullName { get; set; }
|
||||
public string FacilityId { get; set; }
|
||||
public string FacilityName { get; set; }
|
||||
public string Address1 { get; set; }
|
||||
public string Address2 { get; set; }
|
||||
public string City { get; set; }
|
||||
public string ProvinceCode { get; set; }
|
||||
public string EntProvinceCode { get; set; }
|
||||
public string Province { get; set; }
|
||||
public string CountryCode { get; set; }
|
||||
public string EntCountryCode { get; set; }
|
||||
public string Country { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string Telephone { get; set; }
|
||||
public List<String> Ratings { get; } = new List<string>();
|
||||
}
|
||||
|
||||
public class MarkerData
|
||||
{
|
||||
public MarkerData(SysId courseId, short holes, string holesCaption)
|
||||
{
|
||||
this.CourseId = courseId;
|
||||
this.Holes = holes;
|
||||
this.HolesCaption = holesCaption;
|
||||
}
|
||||
public SysId CourseId { get; }
|
||||
public SysId MarkerId { get; set; }
|
||||
public string Caption { get { return StringUtil.TrimWhitespace(StringUtil.OnBlank(Tee) + " " + StringUtil.OnBlank(HolesCaption) + " " + StringUtil.OnBlank(Gender)); } }
|
||||
public short Holes { get; }
|
||||
public string HolesCaption { get; }
|
||||
public string Tee { get; set; }
|
||||
public string Gender { get; set; }
|
||||
public short SlopeRating { get; set; }
|
||||
public short Par { get; set; }
|
||||
public decimal CourseRating { get; set; }
|
||||
public decimal BogeyRating { get; set; }
|
||||
public short Length { get; set; }
|
||||
public bool IsValid()
|
||||
{
|
||||
if (!SysId.IsValid(CourseId)) return false;
|
||||
if (!SysId.IsValid(MarkerId)) return false;
|
||||
if (Holes < 1) return false;
|
||||
if (StringUtil.IsBlank(Tee)) return false;
|
||||
if (StringUtil.IsBlank(Gender)) return false;
|
||||
if (SlopeRating == 0) return false;
|
||||
if (Par == 0) return false;
|
||||
if (CourseRating == 0) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private Config config;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public SaxGolfUsgaDataLookup(Config config) : base(config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
|
||||
public override string ProcessDescription => "Golf Course Lookup";
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Run the process.
|
||||
protected override bool Run()
|
||||
{
|
||||
return OK;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// USGA data lookup.
|
||||
//------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Data elements that may be required in subsequent requests etc.
|
||||
/// </summary>
|
||||
private class UsgaRequestData
|
||||
{
|
||||
public DateTime PopulatedAt { get; } = DateTime.Now;
|
||||
public Dictionary<String, SacsHttpCookieData> Cookies { get; } = new Dictionary<String, SacsHttpCookieData>();
|
||||
public String RequestVerificationToken { get; set; }
|
||||
}
|
||||
private UsgaRequestData usgaRequestData = null;
|
||||
/// <summary>
|
||||
/// Populate the request data from the Home Page response.
|
||||
/// </summary>
|
||||
private void PopulateUsgaRequestData(SacsHttpResponseData response)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!StringUtil.IsBlank(response.Data))
|
||||
{
|
||||
usgaRequestData = new UsgaRequestData();
|
||||
|
||||
// Cookies.
|
||||
usgaRequestData.Cookies.AddRange(response.Cookies);
|
||||
|
||||
// Parse HTML.
|
||||
HtmlDocument htmlDoc = new HtmlDocument();
|
||||
htmlDoc.LoadHtml(response.Data);
|
||||
|
||||
// Locate verification token.
|
||||
HtmlNode verificationTokenNode = htmlDoc.DocumentNode.SelectSingleNode("//input[@name='__RequestVerificationToken']");
|
||||
if (verificationTokenNode != null)
|
||||
{
|
||||
usgaRequestData.RequestVerificationToken = verificationTokenNode.GetAttributeValue("value", "");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Warn(SysUtil.Fn + " " + ExceptionUtil.GetLogMessage(exc, "populating USGA request data", 0));
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Get the USGA request data to extract useful elements, eg. verification token and cookies.
|
||||
/// <para>- Request data is refreshed periodically in case of expiry.</para>
|
||||
/// </summary>
|
||||
private UsgaRequestData GetUsgaRequestData()
|
||||
{
|
||||
if (usgaRequestData == null || (DateTime.Now - usgaRequestData.PopulatedAt).TotalMinutes > 15)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
// Query countries on USGA website.
|
||||
CancellationToken cancelToken = new CancellationToken();
|
||||
SacsHttpRequestData request = new SacsHttpRequestData(cancelToken) { Url = "https://ncrdb.usga.org/" };
|
||||
SacsHttpResponseData response = SacsHttpClient.DoRequest(request).Result;
|
||||
String data = response.Data;
|
||||
|
||||
PopulateUsgaRequestData(response);
|
||||
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Warn(SysUtil.Fn + " " + ExceptionUtil.GetLogMessage(exc, "querying USGA request data", 0));
|
||||
}
|
||||
}
|
||||
return usgaRequestData;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// USGA Countries.
|
||||
/// <para>- Countries are listed on the home page of the USGA site in a dropdown.</para>
|
||||
/// </summary>
|
||||
public List<CountryData> LookupUsgaCountries()
|
||||
{
|
||||
List<CountryData> usgaCountries = new List<CountryData>();
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
// Query countries on USGA website.
|
||||
CancellationToken cancelToken = new CancellationToken();
|
||||
SacsHttpRequestData request = new SacsHttpRequestData(cancelToken) { Url = "https://ncrdb.usga.org/" };
|
||||
SacsHttpResponseData response = SacsHttpClient.DoRequest(request).Result;
|
||||
String data = response.Data;
|
||||
PopulateUsgaRequestData(response);
|
||||
|
||||
if (!StringUtil.IsBlank(response.Data))
|
||||
{
|
||||
// Parse HTML.
|
||||
HtmlDocument htmlDoc = new HtmlDocument();
|
||||
htmlDoc.LoadHtml(response.Data);
|
||||
|
||||
// Locate countries list.
|
||||
HtmlNode countriesNode = htmlDoc.GetElementbyId("ddlCountries");
|
||||
if (countriesNode != null)
|
||||
{
|
||||
foreach (HtmlNode countryOptionNode in countriesNode.SelectNodes("./option"))
|
||||
{
|
||||
String countryCode = StringUtil.TrimWhitespace(countryOptionNode.GetAttributeValue("value", ""));
|
||||
String countryName = StringUtil.TrimWhitespace(WebUtil.HtmlDecode(countryOptionNode.InnerText));
|
||||
// Ignore blank code or name.
|
||||
if (StringUtil.IsBlank(countryCode) || countryCode.EqualsCI("(Select)") || StringUtil.IsBlank(countryName)) continue;
|
||||
// Ignore 'Select' item.
|
||||
if (countryCode.EqualsCI("(Select)")) continue;
|
||||
// Ignore US States and territories.
|
||||
if (countryCode.StartsWithCI("US-") || countryCode.StartsWithCI("UM-")) continue;
|
||||
// Ignore codes that aren't 3 chars.
|
||||
if (countryCode.Length != 3) continue;
|
||||
// Add country to list.
|
||||
usgaCountries.Add(new CountryData() { Code = countryCode, Name = countryName });
|
||||
}
|
||||
}
|
||||
else SaLogger.Warn(SysUtil.Fn + " Countries node not found.");
|
||||
}
|
||||
|
||||
// Store countries.
|
||||
List<SaxReferenceGolfMgr.UsgaCountryData> countriesData = new List<SaxReferenceGolfMgr.UsgaCountryData>();
|
||||
foreach (CountryData usgaCountry in usgaCountries)
|
||||
{
|
||||
SaxReferenceGolfMgr.UsgaCountryData countryData = new SaxReferenceGolfMgr.UsgaCountryData();
|
||||
countryData.CountryCode = usgaCountry.Code;
|
||||
countryData.CountryName = usgaCountry.Name;
|
||||
countriesData.Add(countryData);
|
||||
}
|
||||
ReferenceGolfMgr.BulkUpdateUsgaCountries(countriesData, config.DbUpdateInfo);
|
||||
if (ReferenceGolfMgr.HasErrorState())
|
||||
SaLogger.Warn($"{SysUtil.Fn} Error bulk updating USGA countries: {ReferenceGolfMgr.ErrorStateMessage}.");
|
||||
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Warn(SysUtil.Fn + " " + ExceptionUtil.GetLogMessage(exc, "querying USGA countries", 0));
|
||||
}
|
||||
return usgaCountries;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// USGA Provinces for country.
|
||||
/// </summary>
|
||||
public List<ProvinceData> LookupUsgaProvincesForCountry(String countryCode)
|
||||
{
|
||||
List<ProvinceData> usgaProvinces = new List<ProvinceData>();
|
||||
if (StringUtil.IsBlank(countryCode)) return usgaProvinces;
|
||||
|
||||
UsgaRequestData usgaRequestData = GetUsgaRequestData();
|
||||
|
||||
try
|
||||
{
|
||||
// Query countries on USGA website.
|
||||
CancellationToken cancelToken = new CancellationToken();
|
||||
SacsHttpRequestData request = new SacsHttpRequestData(cancelToken) { Url = "https://ncrdb.usga.org/NCRListing?handler=LoadStates" };
|
||||
// Headers.
|
||||
request.RequestHeaders.Add(HeaderNames.Accept, "*/*");
|
||||
request.RequestHeaders.Add(HeaderNames.AcceptEncoding, "gzip, deflate, br, zstd");
|
||||
request.RequestHeaders.Add(HeaderNames.AcceptLanguage, "en-GB,en;q=0.9");
|
||||
request.RequestHeaders.Add(HeaderNames.DNT, "1");
|
||||
request.RequestHeaders.Add(HeaderNames.Origin, "https://ncrdb.usga.org/");
|
||||
request.RequestHeaders.Add("Priority", "u=1, i");
|
||||
request.RequestHeaders.Add(HeaderNames.Referer, "https://ncrdb.usga.org/");
|
||||
if (!StringUtil.IsBlank(usgaRequestData.RequestVerificationToken))
|
||||
request.RequestHeaders.Add("Requestverificationtoken", usgaRequestData.RequestVerificationToken);
|
||||
// Content.
|
||||
request.FormData.Add(new KeyValuePair<string, string>("countryCode", countryCode));
|
||||
request.ContentHeaders.Add(HeaderNames.ContentType, "application/x-www-form-urlencoded; charset=UTF-8");
|
||||
// Request.
|
||||
SacsHttpResponseData response = SacsHttpClient.DoRequest(request).Result;
|
||||
String data = response.Data;
|
||||
|
||||
if (!StringUtil.IsBlank(response.Data))
|
||||
{
|
||||
dynamic provincesJson = SacsJsonHelper.DeserializeJsonObject(response.Data);
|
||||
if (provincesJson is JArray)
|
||||
{
|
||||
JArray provincesArray = provincesJson as JArray;
|
||||
foreach (var provinceToken in provincesArray)
|
||||
{
|
||||
// Province properties.
|
||||
ProvinceData provinceData = new ProvinceData();
|
||||
foreach (var propertyToken in provinceToken)
|
||||
{
|
||||
if (propertyToken is JProperty)
|
||||
{
|
||||
JProperty provinceProperty = propertyToken as JProperty;
|
||||
String propertyName = provinceProperty.Name;
|
||||
String propertyStringValue = StringUtil.TrimWhitespace(provinceProperty.Value.ToString());
|
||||
if (propertyName.EqualsCI("text"))
|
||||
provinceData.Name = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("value"))
|
||||
provinceData.Code = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("group")) { }
|
||||
else if (propertyName.EqualsCI("selected")) { }
|
||||
else if (propertyName.EqualsCI("disabled")) { }
|
||||
}
|
||||
}
|
||||
if (!StringUtil.IsBlank(provinceData.Code) && !provinceData.Code.EqualsCI("(Select)") && !StringUtil.IsBlank(provinceData.Name))
|
||||
usgaProvinces.Add(provinceData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store provinces.
|
||||
List<SaxReferenceGolfMgr.UsgaProvinceData> provincesData = new List<SaxReferenceGolfMgr.UsgaProvinceData>();
|
||||
foreach (ProvinceData usgaProvince in usgaProvinces)
|
||||
{
|
||||
SaxReferenceGolfMgr.UsgaProvinceData provinceData = new SaxReferenceGolfMgr.UsgaProvinceData();
|
||||
provinceData.CountryCode = countryCode;
|
||||
provinceData.ProvinceCode = usgaProvince.Code;
|
||||
provinceData.ProvinceName = usgaProvince.Name;
|
||||
provincesData.Add(provinceData);
|
||||
}
|
||||
ReferenceGolfMgr.BulkUpdateUsgaProvinces(provincesData, config.DbUpdateInfo);
|
||||
if (ReferenceGolfMgr.HasErrorState())
|
||||
SaLogger.Warn($"{SysUtil.Fn} Error bulk updating USGA provinces: {ReferenceGolfMgr.ErrorStateMessage}.");
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Warn(SysUtil.Fn + " " + ExceptionUtil.GetLogMessage(exc, $"querying USGA provinces for {countryCode}", 0));
|
||||
}
|
||||
return usgaProvinces;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// USGA Courses for country.
|
||||
/// </summary>
|
||||
public List<CourseData> LookupUsgaCoursesForCountryAndProvince(String countryCode, String provinceCode)
|
||||
{
|
||||
List<CourseData> usgaCourses = new List<CourseData>();
|
||||
if (StringUtil.IsBlank(countryCode) || StringUtil.IsBlank(provinceCode)) return usgaCourses;
|
||||
|
||||
UsgaRequestData usgaRequestData = GetUsgaRequestData();
|
||||
|
||||
try
|
||||
{
|
||||
// Query countries on USGA website.
|
||||
CancellationToken cancelToken = new CancellationToken();
|
||||
SacsHttpRequestData request = new SacsHttpRequestData(cancelToken) { Url = "https://ncrdb.usga.org/NCRListing?handler=LoadCourses" };
|
||||
// Headers.
|
||||
request.RequestHeaders.Add(HeaderNames.Accept, "*/*");
|
||||
request.RequestHeaders.Add(HeaderNames.AcceptEncoding, "gzip, deflate, br, zstd");
|
||||
request.RequestHeaders.Add(HeaderNames.AcceptLanguage, "en-GB,en;q=0.9");
|
||||
request.RequestHeaders.Add(HeaderNames.DNT, "1");
|
||||
request.RequestHeaders.Add(HeaderNames.Origin, "https://ncrdb.usga.org/");
|
||||
request.RequestHeaders.Add("Priority", "u=1, i");
|
||||
request.RequestHeaders.Add(HeaderNames.Referer, "https://ncrdb.usga.org/");
|
||||
if (!StringUtil.IsBlank(usgaRequestData.RequestVerificationToken))
|
||||
request.RequestHeaders.Add("Requestverificationtoken", usgaRequestData.RequestVerificationToken);
|
||||
// Content.
|
||||
request.FormData.Add(new KeyValuePair<string, string>("clubName", ""));
|
||||
request.FormData.Add(new KeyValuePair<string, string>("clubCity", ""));
|
||||
request.FormData.Add(new KeyValuePair<string, string>("clubState", provinceCode));
|
||||
request.FormData.Add(new KeyValuePair<string, string>("clubCountry", countryCode));
|
||||
request.ContentHeaders.Add(HeaderNames.ContentType, "application/x-www-form-urlencoded; charset=UTF-8");
|
||||
// Request.
|
||||
SacsHttpResponseData response = SacsHttpClient.DoRequest(request).Result;
|
||||
String data = response.Data;
|
||||
|
||||
if (!StringUtil.IsBlank(response.Data))
|
||||
{
|
||||
HashSet<String> UnrecognisedCourseProperties = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
dynamic coursesJson = SacsJsonHelper.DeserializeJsonObject(response.Data);
|
||||
if (coursesJson is JArray)
|
||||
{
|
||||
JArray coursesArray = coursesJson as JArray;
|
||||
foreach (var courseToken in coursesArray)
|
||||
{
|
||||
// Course properties.
|
||||
CourseData courseData = new CourseData();
|
||||
foreach (var propertyToken in courseToken)
|
||||
{
|
||||
if (propertyToken is JProperty)
|
||||
{
|
||||
JProperty courseProperty = propertyToken as JProperty;
|
||||
String propertyName = courseProperty.Name;
|
||||
String propertyStringValue = StringUtil.TrimWhitespace(courseProperty.Value.ToString());
|
||||
if (propertyName.EqualsCI("courseId"))
|
||||
courseData.CourseId = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("legacyCRPCourseId"))
|
||||
courseData.LegacyCrpCourseId = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("courseName"))
|
||||
courseData.CourseName = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("facilityId"))
|
||||
courseData.FacilityId = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("facilityName"))
|
||||
courseData.FacilityName = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("fullName"))
|
||||
courseData.FullName = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("address1"))
|
||||
courseData.Address1 = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("address2"))
|
||||
courseData.Address2 = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("city"))
|
||||
courseData.City = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("entStateCode"))
|
||||
courseData.EntProvinceCode = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("state"))
|
||||
courseData.ProvinceCode = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("stateDisplay"))
|
||||
courseData.Province = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("country"))
|
||||
courseData.CountryCode = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("entCountryCode"))
|
||||
courseData.EntCountryCode = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("email"))
|
||||
courseData.Email = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("telephone"))
|
||||
courseData.Telephone = propertyStringValue;
|
||||
else if (propertyName.EqualsCI("ratings"))
|
||||
{
|
||||
if (courseProperty.Value.Count() > 0)
|
||||
courseData.Ratings.Add("");
|
||||
}
|
||||
else UnrecognisedCourseProperties.Add(propertyName);
|
||||
}
|
||||
}
|
||||
if (!StringUtil.IsBlank(courseData.CourseId) && !StringUtil.IsBlank(courseData.CourseName))
|
||||
{
|
||||
// Populate caption and add to list.
|
||||
SetCourseCaption(courseData);
|
||||
usgaCourses.Add(courseData);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (UnrecognisedCourseProperties.Any())
|
||||
SaLogger.Warn(SysUtil.Fn + " Unrecognised USGA course properties: " + String.Join(", ", UnrecognisedCourseProperties));
|
||||
}
|
||||
|
||||
// Store countries.
|
||||
Dictionary<String, SaxReferenceGolfMgr.UsgaCountryData> countriesData = new Dictionary<String, SaxReferenceGolfMgr.UsgaCountryData>();
|
||||
foreach (CourseData usgaCourse in usgaCourses)
|
||||
{
|
||||
SaxReferenceGolfMgr.UsgaCountryData countryData = new SaxReferenceGolfMgr.UsgaCountryData();
|
||||
countryData.CountryCode = usgaCourse.CountryCode;
|
||||
countryData.EntCountryCode = usgaCourse.EntCountryCode;
|
||||
countriesData[countryData.CountryCode] = countryData;
|
||||
}
|
||||
ReferenceGolfMgr.BulkUpdateUsgaCountries(countriesData.Values.ToList(), config.DbUpdateInfo);
|
||||
if (ReferenceGolfMgr.HasErrorState())
|
||||
SaLogger.Warn($"{SysUtil.Fn} Error bulk updating USGA countries: {ReferenceGolfMgr.ErrorStateMessage}.");
|
||||
|
||||
// Store provinces.
|
||||
Dictionary<String, SaxReferenceGolfMgr.UsgaProvinceData> provincesData = new Dictionary<String, SaxReferenceGolfMgr.UsgaProvinceData>();
|
||||
foreach (CourseData usgaCourse in usgaCourses)
|
||||
{
|
||||
SaxReferenceGolfMgr.UsgaProvinceData provinceData = new SaxReferenceGolfMgr.UsgaProvinceData();
|
||||
provinceData.CountryCode = usgaCourse.CountryCode;
|
||||
provinceData.ProvinceCode = usgaCourse.ProvinceCode;
|
||||
provinceData.EntProvinceCode = usgaCourse.EntProvinceCode;
|
||||
provincesData[provinceData.ProvinceCode] = provinceData;
|
||||
}
|
||||
ReferenceGolfMgr.BulkUpdateUsgaProvinces(provincesData.Values.ToList(), config.DbUpdateInfo);
|
||||
if (ReferenceGolfMgr.HasErrorState())
|
||||
SaLogger.Warn($"{SysUtil.Fn} Error bulk updating USGA provinces: {ReferenceGolfMgr.ErrorStateMessage}.");
|
||||
|
||||
// Store facilities.
|
||||
Dictionary<SysId, SaxReferenceGolfMgr.UsgaFacilityData> facilitiesData = new Dictionary<SysId, SaxReferenceGolfMgr.UsgaFacilityData>();
|
||||
foreach (CourseData usgaCourse in usgaCourses)
|
||||
{
|
||||
if (StringUtil.IsBlank(usgaCourse.FacilityId)) continue;
|
||||
SaxReferenceGolfMgr.UsgaFacilityData facilityData = new SaxReferenceGolfMgr.UsgaFacilityData();
|
||||
facilityData.ProvinceCode = provinceCode;
|
||||
facilityData.FacilityId = StringUtil.ToSysId(usgaCourse.FacilityId);
|
||||
facilityData.FacilityName = usgaCourse.FacilityName;
|
||||
if (!SysId.IsValid(facilityData.FacilityId))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Invalid Facility Id for {usgaCourse.CourseName}: {usgaCourse.FacilityId}.");
|
||||
continue;
|
||||
}
|
||||
facilitiesData[facilityData.FacilityId] = facilityData;
|
||||
}
|
||||
ReferenceGolfMgr.BulkUpdateUsgaFacilities(facilitiesData.Values.ToList(), config.DbUpdateInfo);
|
||||
if (ReferenceGolfMgr.HasErrorState())
|
||||
SaLogger.Warn($"{SysUtil.Fn} Error bulk updating USGA facilities: {ReferenceGolfMgr.ErrorStateMessage}.");
|
||||
|
||||
// Store courses.
|
||||
Dictionary<SysId, SaxReferenceGolfMgr.UsgaCourseData> coursesData = new Dictionary<SysId, SaxReferenceGolfMgr.UsgaCourseData>();
|
||||
foreach (CourseData usgaCourse in usgaCourses)
|
||||
{
|
||||
if (StringUtil.IsBlank(usgaCourse.CourseId)) continue;
|
||||
SaxReferenceGolfMgr.UsgaCourseData courseData = new SaxReferenceGolfMgr.UsgaCourseData();
|
||||
courseData.FacilityId = StringUtil.ToSysId(usgaCourse.FacilityId);
|
||||
courseData.CourseId = StringUtil.ToSysId(usgaCourse.CourseId);
|
||||
courseData.LegacyCrpCourseId = StringUtil.ToSysId(usgaCourse.LegacyCrpCourseId);
|
||||
courseData.Caption = usgaCourse.Caption;
|
||||
courseData.CourseName = usgaCourse.CourseName;
|
||||
courseData.FullName = usgaCourse.FullName;
|
||||
courseData.Address1 = usgaCourse.Address1;
|
||||
courseData.Address2 = usgaCourse.Address2;
|
||||
courseData.City = usgaCourse.City;
|
||||
courseData.Email = usgaCourse.Email;
|
||||
courseData.Telephone = usgaCourse.Telephone;
|
||||
if (!SysId.IsValid(courseData.CourseId))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Invalid Course Id for {usgaCourse.CourseName}: {usgaCourse.CourseId}.");
|
||||
continue;
|
||||
}
|
||||
coursesData[courseData.CourseId] = courseData;
|
||||
}
|
||||
ReferenceGolfMgr.BulkUpdateUsgaCourses(coursesData.Values.ToList(), config.DbUpdateInfo);
|
||||
if (ReferenceGolfMgr.HasErrorState())
|
||||
SaLogger.Warn($"{SysUtil.Fn} Error bulk updating USGA courses: {ReferenceGolfMgr.ErrorStateMessage}.");
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Warn(SysUtil.Fn + " " + ExceptionUtil.GetLogMessage(exc, $"querying USGA courses for {countryCode}", 0));
|
||||
}
|
||||
return usgaCourses;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine facility and course names as required to generate course caption.
|
||||
/// <para>- NB Course name is sufficient if same as facility name.</para>
|
||||
/// </summary>
|
||||
/// <param name="course"></param>
|
||||
private void SetCourseCaption(CourseData course)
|
||||
{
|
||||
// Facility (club) name.
|
||||
// - Strip id in brackets + anything following and remove generic phrases (golf club, hotel etc).
|
||||
String facilityName = CleanseFacilityName(Regex.Replace(course.FacilityName, "\\s*\\(\\s*\\d+\\s*\\).*$", ""));
|
||||
// Course name.
|
||||
// - Remove id in brackets (occasionally found).
|
||||
// - Remove generic phrases (including for facility name).
|
||||
String courseName = CleanseCourseName(Regex.Replace(course.CourseName, "\\(\\s*\\d+\\s*\\)", ""));
|
||||
course.Caption = courseName;
|
||||
if (!CourseNameMatchesFacility(facilityName, courseName))
|
||||
course.Caption = facilityName + " - " + courseName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanse Facility name by removing generic phrases, eg. golf club, hotel etc.
|
||||
/// </summary>
|
||||
private String CleanseFacilityName(String facilityName)
|
||||
{
|
||||
facilityName = Regex.Replace(facilityName, "Hotel\\s*(and|&)\\s*(Golf|Country)\\s*Club", "", RegexOptions.IgnoreCase);
|
||||
facilityName = Regex.Replace(facilityName, "Hotel,*\\s*Golf\\s*(and|&)\\s*Spa", "", RegexOptions.IgnoreCase);
|
||||
facilityName = Regex.Replace(facilityName, "Golf\\s*(and|&)\\s*(Country|Sailing)\\s*Club", "", RegexOptions.IgnoreCase);
|
||||
facilityName = Regex.Replace(facilityName, "Golf\\s*Club\\s*(and|&)\\s*Hotel", "", RegexOptions.IgnoreCase);
|
||||
facilityName = Regex.Replace(facilityName, "Golf\\s*Club\\s*Ltd", "", RegexOptions.IgnoreCase);
|
||||
facilityName = Regex.Replace(facilityName, "Golf\\s*(Club|Course|Links|Centre|Academy|Resort|Klub)", "", RegexOptions.IgnoreCase);
|
||||
facilityName = Regex.Replace(facilityName, "Club\\s*de\\s*Golf", "", RegexOptions.IgnoreCase);
|
||||
return StringUtil.TrimWhitespace(facilityName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanse Course name by removing generic phrases, including those defined for facility name.
|
||||
/// </summary>
|
||||
private String CleanseCourseName(String facilityName)
|
||||
{
|
||||
// Course name occasionally suffixed with 'Course' but this is meaningful in many cases (old course, new course).
|
||||
//facilityName = Regex.Replace(facilityName, "\\bCourse\\b", "", RegexOptions.IgnoreCase);
|
||||
return CleanseFacilityName(facilityName);
|
||||
}
|
||||
|
||||
private bool CourseNameMatchesFacility(String facilityName, String courseName)
|
||||
{
|
||||
if (courseName.EqualsCI(facilityName)) return true;
|
||||
// Strip selected char/symbols (especially apostrophes which are inconsistent).
|
||||
if (Regex.Replace(courseName, "[\\s-',\\.]", "").EqualsCI(Regex.Replace(facilityName, "[\\s-',\\.]", ""))) return true;
|
||||
// Course name can end on Golf or Course and still match facility.
|
||||
if (Regex.Replace(courseName, "\\s*(Golf|Course)$", "").EqualsCI(facilityName)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Identify the column for each marker property.
|
||||
/// </summary>
|
||||
private class MarkerColumnData
|
||||
{
|
||||
public int ColIdx_Tee { get; set; } = -1;
|
||||
public int ColIdx_Gender { get; set; } = -1;
|
||||
public int ColIdx_Par { get; set; } = -1;
|
||||
public int ColIdx_CourseRating { get; set; } = -1;
|
||||
public int ColIdx_BogeyRating { get; set; } = -1;
|
||||
public int ColIdx_SlopeRating { get; set; } = -1;
|
||||
public int ColIdx_Front9 { get; set; } = -1;
|
||||
public int ColIdx_Back9 { get; set; } = -1;
|
||||
public int ColIdx_Front9CourseRating { get; set; } = -1;
|
||||
public int ColIdx_Back9CourseRating { get; set; } = -1;
|
||||
public int ColIdx_Front9BogeyRating { get; set; } = -1;
|
||||
public int ColIdx_Back9BogeyRating { get; set; } = -1;
|
||||
public int ColIdx_Front9SlopeRating { get; set; } = -1;
|
||||
public int ColIdx_Back9SlopeRating { get; set; } = -1;
|
||||
public int ColIdx_MarkerId { get; set; } = -1;
|
||||
public int ColIdx_Length { get; set; } = -1;
|
||||
}
|
||||
/// <summary>
|
||||
/// USGA Markers for course.
|
||||
/// </summary>
|
||||
public List<MarkerData> LookupUsgaMarkersForCourse(SysId usgaCourseId)
|
||||
{
|
||||
List<MarkerData> usgaMarkers = new List<MarkerData>();
|
||||
if (!SysId.IsValid(usgaCourseId)) return usgaMarkers;
|
||||
MarkerColumnData markerColumns = new MarkerColumnData();
|
||||
|
||||
try
|
||||
{
|
||||
// Query countries on USGA website.
|
||||
CancellationToken cancelToken = new CancellationToken();
|
||||
SacsHttpRequestData request = new SacsHttpRequestData(cancelToken) { Url = $"https://ncrdb.usga.org/courseTeeInfo?CourseID={usgaCourseId}" };
|
||||
SacsHttpResponseData response = SacsHttpClient.DoRequest(request).Result;
|
||||
String data = response.Data;
|
||||
|
||||
if (!StringUtil.IsBlank(response.Data))
|
||||
{
|
||||
// Parse HTML.
|
||||
HtmlDocument htmlDoc = new HtmlDocument();
|
||||
htmlDoc.LoadHtml(response.Data);
|
||||
|
||||
// Locate countries list.
|
||||
HtmlNode teeTableNode = htmlDoc.GetElementbyId("gvTee");
|
||||
if (teeTableNode != null)
|
||||
{
|
||||
// Identify marker columns.
|
||||
int teeColumnIdx = -1;
|
||||
foreach (HtmlNode teeHeaderNode in teeTableNode.SelectNodes("./tr[1]/th"))
|
||||
{
|
||||
teeColumnIdx++;
|
||||
String headerText = StringUtil.TrimWhitespace(WebUtil.HtmlDecode(teeHeaderNode.InnerText));
|
||||
if (StringUtil.IsBlank(headerText) || headerText.EqualsCI("CH"))
|
||||
{
|
||||
// Columns ignored.
|
||||
}
|
||||
else if (headerText.EqualsCI("Tee Name")) markerColumns.ColIdx_Tee = teeColumnIdx;
|
||||
else if (headerText.EqualsCI("Gender")) markerColumns.ColIdx_Gender = teeColumnIdx;
|
||||
else if (headerText.EqualsCI("Par")) markerColumns.ColIdx_Par = teeColumnIdx;
|
||||
else if (headerText.EqualsCI("TeeId")) markerColumns.ColIdx_MarkerId = teeColumnIdx;
|
||||
else if (headerText.EqualsCI("Length")) markerColumns.ColIdx_Length = teeColumnIdx;
|
||||
// Specific part-course (9 holes) values.
|
||||
else if (headerText.EqualsCI("RatingF9")) markerColumns.ColIdx_Front9CourseRating = teeColumnIdx;
|
||||
else if (headerText.EqualsCI("RatingB9")) markerColumns.ColIdx_Back9CourseRating = teeColumnIdx;
|
||||
else if (headerText.EqualsCI("Front (9)")) markerColumns.ColIdx_Front9 = teeColumnIdx;
|
||||
else if (headerText.EqualsCI("Back (9)")) markerColumns.ColIdx_Back9 = teeColumnIdx;
|
||||
else if (headerText.EqualsCI("Bogey Rating (F9)")) markerColumns.ColIdx_Front9BogeyRating = teeColumnIdx;
|
||||
else if (headerText.EqualsCI("Bogey Rating (B9)")) markerColumns.ColIdx_Back9BogeyRating = teeColumnIdx;
|
||||
else if (headerText.EqualsCI("Slope (F9)")) markerColumns.ColIdx_Front9SlopeRating = teeColumnIdx;
|
||||
else if (headerText.EqualsCI("Slope (B9)")) markerColumns.ColIdx_Back9SlopeRating = teeColumnIdx;
|
||||
// Less specific full-course (commonly 18 holes) values.
|
||||
else if (headerText.IsMatchCI("Course Rating")) markerColumns.ColIdx_CourseRating = teeColumnIdx;
|
||||
else if (headerText.IsMatchCI("Bogey Rating")) markerColumns.ColIdx_BogeyRating = teeColumnIdx;
|
||||
else if (headerText.IsMatchCI("Slope Rating")) markerColumns.ColIdx_SlopeRating = teeColumnIdx;
|
||||
else SaLogger.Info($"{SysUtil.Fn} Unexpected Marker column header: {headerText}");
|
||||
}
|
||||
foreach (HtmlNode teeRowNode in teeTableNode.SelectNodes("./tr[position()>1]"))
|
||||
{
|
||||
MarkerData markerData18 = new MarkerData(usgaCourseId, 18, "18");
|
||||
MarkerData markerDataF9 = new MarkerData(usgaCourseId, 9, "F9");
|
||||
MarkerData markerDataB9 = new MarkerData(usgaCourseId, 9, "B9");
|
||||
teeColumnIdx = -1;
|
||||
foreach (HtmlNode teePropertyNode in teeRowNode.SelectNodes("./td"))
|
||||
{
|
||||
teeColumnIdx++;
|
||||
String propertyText = StringUtil.TrimWhitespace(WebUtil.HtmlDecode(teePropertyNode.InnerText));
|
||||
if (markerColumns.ColIdx_Tee == teeColumnIdx)
|
||||
{
|
||||
markerData18.Tee = propertyText;
|
||||
markerDataF9.Tee = propertyText;
|
||||
markerDataB9.Tee = propertyText;
|
||||
}
|
||||
else if (markerColumns.ColIdx_Gender == teeColumnIdx)
|
||||
{
|
||||
markerData18.Gender = propertyText;
|
||||
markerDataF9.Gender = propertyText;
|
||||
markerDataB9.Gender = propertyText;
|
||||
}
|
||||
else if (markerColumns.ColIdx_Par == teeColumnIdx)
|
||||
{
|
||||
markerData18.Par = StringUtil.ToShort(propertyText);
|
||||
}
|
||||
else if (markerColumns.ColIdx_CourseRating == teeColumnIdx)
|
||||
{
|
||||
markerData18.CourseRating = StringUtil.ToDecimal(propertyText);
|
||||
}
|
||||
else if (markerColumns.ColIdx_BogeyRating == teeColumnIdx)
|
||||
{
|
||||
markerData18.BogeyRating = StringUtil.ToDecimal(propertyText);
|
||||
}
|
||||
else if (markerColumns.ColIdx_SlopeRating == teeColumnIdx)
|
||||
{
|
||||
markerData18.SlopeRating = StringUtil.ToShort(propertyText);
|
||||
}
|
||||
else if (markerColumns.ColIdx_Front9CourseRating == teeColumnIdx)
|
||||
{
|
||||
if (markerDataF9.CourseRating == 0)
|
||||
markerDataF9.CourseRating = StringUtil.ToDecimal(propertyText);
|
||||
}
|
||||
else if (markerColumns.ColIdx_Back9CourseRating == teeColumnIdx)
|
||||
{
|
||||
if (markerDataB9.CourseRating == 0)
|
||||
markerDataB9.CourseRating = StringUtil.ToDecimal(propertyText);
|
||||
}
|
||||
else if (markerColumns.ColIdx_Front9 == teeColumnIdx)
|
||||
{
|
||||
// Format: CourseRating / SlopeRating.
|
||||
markerDataF9.CourseRating = StringUtil.ToDecimal(Regex.Replace(propertyText, "\\s*/.*$", ""));
|
||||
markerDataF9.SlopeRating = StringUtil.ToShort(Regex.Replace(propertyText, "^.*/\\s*", ""));
|
||||
}
|
||||
else if (markerColumns.ColIdx_Back9 == teeColumnIdx)
|
||||
{
|
||||
// Format: CourseRating / SlopeRating.
|
||||
markerDataB9.CourseRating = StringUtil.ToDecimal(Regex.Replace(propertyText, "\\s*/.*$", ""));
|
||||
markerDataB9.SlopeRating = StringUtil.ToShort(Regex.Replace(propertyText, "^.*/\\s*", ""));
|
||||
}
|
||||
else if (markerColumns.ColIdx_Front9BogeyRating == teeColumnIdx)
|
||||
{
|
||||
markerDataF9.BogeyRating = StringUtil.ToDecimal(propertyText);
|
||||
}
|
||||
else if (markerColumns.ColIdx_Back9BogeyRating == teeColumnIdx)
|
||||
{
|
||||
markerDataB9.BogeyRating = StringUtil.ToDecimal(propertyText);
|
||||
}
|
||||
else if (markerColumns.ColIdx_Front9SlopeRating == teeColumnIdx)
|
||||
{
|
||||
if (markerDataF9.SlopeRating == 0)
|
||||
markerDataF9.SlopeRating = StringUtil.ToShort(propertyText);
|
||||
}
|
||||
else if (markerColumns.ColIdx_Back9SlopeRating == teeColumnIdx)
|
||||
{
|
||||
// Back 9 Slope Rating populated with value for Front 9.
|
||||
//markerDataB9.SlopeRating = StringUtil.ToInt(propertyText);
|
||||
}
|
||||
else if (markerColumns.ColIdx_MarkerId == teeColumnIdx)
|
||||
{
|
||||
markerData18.MarkerId = StringUtil.ToSysId(propertyText);
|
||||
// Unclear if Tee Id is relevant to 9 holes.
|
||||
//markerDataF9.MarkerId = StringUtil.ToSysId(propertyText);
|
||||
//markerDataB9.MarkerId = StringUtil.ToSysId(propertyText);
|
||||
}
|
||||
else if (markerColumns.ColIdx_Length == teeColumnIdx)
|
||||
{
|
||||
markerData18.Length = StringUtil.ToShort(propertyText);
|
||||
}
|
||||
}
|
||||
|
||||
// Add valid markers to list.
|
||||
if (markerData18.IsValid())
|
||||
usgaMarkers.Add(markerData18);
|
||||
if (markerDataF9.IsValid())
|
||||
usgaMarkers.Add(markerDataF9);
|
||||
if (markerDataB9.IsValid())
|
||||
usgaMarkers.Add(markerDataB9);
|
||||
}
|
||||
}
|
||||
else SaLogger.Warn(SysUtil.Fn + " Tee table not found.");
|
||||
}
|
||||
|
||||
// Store markers.
|
||||
List<SaxReferenceGolfMgr.UsgaMarkerData> markersData = new List<SaxReferenceGolfMgr.UsgaMarkerData>();
|
||||
foreach (MarkerData usgaMarker in usgaMarkers)
|
||||
{
|
||||
if (!SysId.IsValid(usgaMarker.CourseId)) continue;
|
||||
//if (!SysId.IsValid(usgaMarker.MarkerId)) continue;
|
||||
if (StringUtil.IsBlank(usgaMarker.Caption)) continue;
|
||||
SaxReferenceGolfMgr.UsgaMarkerData markerData = new SaxReferenceGolfMgr.UsgaMarkerData();
|
||||
markerData.CourseId = usgaMarker.CourseId;
|
||||
markerData.MarkerId = usgaMarker.MarkerId;
|
||||
markerData.Caption = usgaMarker.Caption;
|
||||
markerData.Holes = usgaMarker.Holes;
|
||||
markerData.HolesCaption = usgaMarker.HolesCaption;
|
||||
markerData.Tee = usgaMarker.Tee;
|
||||
markerData.Gender = usgaMarker.Gender;
|
||||
markerData.SlopeRating = usgaMarker.SlopeRating;
|
||||
markerData.Par = usgaMarker.Par;
|
||||
markerData.CourseRating = usgaMarker.CourseRating;
|
||||
markerData.BogeyRating = usgaMarker.BogeyRating;
|
||||
markerData.Length = usgaMarker.Length;
|
||||
markersData.Add(markerData);
|
||||
}
|
||||
ReferenceGolfMgr.BulkUpdateUsgaMarkers(markersData, config.DbUpdateInfo);
|
||||
if (ReferenceGolfMgr.HasErrorState())
|
||||
SaLogger.Warn($"{SysUtil.Fn} Error bulk updating USGA markers: {ReferenceGolfMgr.ErrorStateMessage}.");
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Warn(SysUtil.Fn + " " + ExceptionUtil.GetLogMessage(exc, $"querying USGA markers for course Id: {usgaCourseId}", 0));
|
||||
}
|
||||
return usgaMarkers;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
}
|
||||
//======================================================================
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Sarand.Net.Standard.Base.Dbas;
|
||||
using Sarand.Net.Standard.Proc.Excel;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Procs;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Procs
|
||||
{
|
||||
//=======================================================================
|
||||
public abstract class SaxProcess_Web : SaxProcess_Base
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
public new class Config : SaxProcess_Base.Config
|
||||
{
|
||||
protected Config(SaxSessionContext sessionContext, DbUpdateInfo dbUpdateInfo) : base(sessionContext, dbUpdateInfo)
|
||||
{ }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private Config config;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
protected SaxProcess_Web(Config config) : base(config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Excel Spreadsheet utilities.
|
||||
//-------------------------------------------------------------------
|
||||
protected override SaMsExcelColumnHeading_Config GetExcelColumnHeadingConfig()
|
||||
{
|
||||
SaMsExcelColumnHeading_Config configHeading = base.GetExcelColumnHeadingConfig();
|
||||
return configHeading;
|
||||
}
|
||||
|
||||
protected override SaMsExcelStyleAttr GetExcelDefaultWorkbookStyle()
|
||||
{
|
||||
SaMsExcelStyleAttr styleAttr = base.GetExcelDefaultWorkbookStyle();
|
||||
return styleAttr;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
Reference in New Issue
Block a user