mirror of
https://github.com/alexhopeoconnor/bom-local-service.git
synced 2026-10-03 21:21:59 +10:00
Refactor configuration system and remove OpenAPI
- Remove OpenAPI/Swagger support and all related code - Standardize configuration: remove hardcoded defaults, all defaults from appsettings.json - Configuration values throw exceptions if missing (except Debug:Enabled defaults to false) - Refactor CacheManagementService: extract ProcessLocationsAsync helper, remove duplicate code - Combine UpdateStaggerSeconds and LocationStaggerSeconds into single LocationStaggerSeconds config - Move config reads to constructors for better performance - Update README and docker-compose.yml to reflect configuration changes
This commit is contained in:
@@ -7,7 +7,6 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.11" />
|
|
||||||
<PackageReference Include="Microsoft.Playwright" Version="1.57.0" />
|
<PackageReference Include="Microsoft.Playwright" Version="1.57.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -60,8 +60,19 @@ public class RadarController : ControllerBase
|
|||||||
{
|
{
|
||||||
// Get cache status to include in 404 response
|
// Get cache status to include in 404 response
|
||||||
// Use cache service directly since this is cache management, not radar-specific
|
// Use cache service directly since this is cache management, not radar-specific
|
||||||
var cacheExpirationMinutes = (int)_configuration.GetValue<double>("CacheExpirationMinutes", 15.5);
|
var cacheExpirationMinutesConfig = _configuration.GetValue<double?>("CacheExpirationMinutes");
|
||||||
var cacheManagementCheckIntervalMinutes = _configuration.GetValue<int>("CacheManagement:CheckIntervalMinutes", 5);
|
if (!cacheExpirationMinutesConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("CacheExpirationMinutes configuration is required. Set it in appsettings.json or via CACHEEXPIRATIONMINUTES environment variable.");
|
||||||
|
}
|
||||||
|
var cacheExpirationMinutes = (int)cacheExpirationMinutesConfig.Value;
|
||||||
|
|
||||||
|
var cacheManagementCheckIntervalMinutesConfig = _configuration.GetValue<int?>("CacheManagement:CheckIntervalMinutes");
|
||||||
|
if (!cacheManagementCheckIntervalMinutesConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("CacheManagement:CheckIntervalMinutes configuration is required. Set it in appsettings.json or via CACHEMANAGEMENT__CHECKINTERVALMINUTES environment variable.");
|
||||||
|
}
|
||||||
|
var cacheManagementCheckIntervalMinutes = cacheManagementCheckIntervalMinutesConfig.Value;
|
||||||
var cacheStatus = await _cacheService.GetCacheStatusAsync(
|
var cacheStatus = await _cacheService.GetCacheStatusAsync(
|
||||||
suburb,
|
suburb,
|
||||||
state,
|
state,
|
||||||
@@ -288,7 +299,14 @@ public class RadarController : ControllerBase
|
|||||||
|
|
||||||
// Validate time range size to prevent excessive data loading
|
// Validate time range size to prevent excessive data loading
|
||||||
// Default: base limit on cache retention, but allow override via config
|
// Default: base limit on cache retention, but allow override via config
|
||||||
var cacheRetentionHours = _configuration.GetValue<int>("CacheRetentionHours", 24);
|
var cacheRetentionHoursConfig = _configuration.GetValue<int?>("CacheRetentionHours");
|
||||||
|
if (!cacheRetentionHoursConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("CacheRetentionHours configuration is required. Set it in appsettings.json or via CACHERETENTIONHOURS environment variable.");
|
||||||
|
}
|
||||||
|
var cacheRetentionHours = cacheRetentionHoursConfig.Value;
|
||||||
|
|
||||||
|
// TimeSeries:MaxTimeRangeHours is optional (nullable)
|
||||||
var configuredMaxHours = _configuration.GetValue<int?>("TimeSeries:MaxTimeRangeHours");
|
var configuredMaxHours = _configuration.GetValue<int?>("TimeSeries:MaxTimeRangeHours");
|
||||||
|
|
||||||
// Use configured value if set, otherwise use cache retention (with minimum of 24 hours)
|
// Use configured value if set, otherwise use cache retention (with minimum of 24 hours)
|
||||||
@@ -331,8 +349,19 @@ public class RadarController : ControllerBase
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Get cache status to include in 404 response
|
// Get cache status to include in 404 response
|
||||||
var cacheExpirationMinutes = (int)_configuration.GetValue<double>("CacheExpirationMinutes", 15.5);
|
var cacheExpirationMinutesConfig = _configuration.GetValue<double?>("CacheExpirationMinutes");
|
||||||
var cacheManagementCheckIntervalMinutes = _configuration.GetValue<int>("CacheManagement:CheckIntervalMinutes", 5);
|
if (!cacheExpirationMinutesConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("CacheExpirationMinutes configuration is required. Set it in appsettings.json or via CACHEEXPIRATIONMINUTES environment variable.");
|
||||||
|
}
|
||||||
|
var cacheExpirationMinutes = (int)cacheExpirationMinutesConfig.Value;
|
||||||
|
|
||||||
|
var cacheManagementCheckIntervalMinutesConfig = _configuration.GetValue<int?>("CacheManagement:CheckIntervalMinutes");
|
||||||
|
if (!cacheManagementCheckIntervalMinutesConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("CacheManagement:CheckIntervalMinutes configuration is required. Set it in appsettings.json or via CACHEMANAGEMENT__CHECKINTERVALMINUTES environment variable.");
|
||||||
|
}
|
||||||
|
var cacheManagementCheckIntervalMinutes = cacheManagementCheckIntervalMinutesConfig.Value;
|
||||||
var cacheStatus = await _cacheService.GetCacheStatusAsync(
|
var cacheStatus = await _cacheService.GetCacheStatusAsync(
|
||||||
suburb,
|
suburb,
|
||||||
state,
|
state,
|
||||||
|
|||||||
+15
-40
@@ -14,39 +14,25 @@ var builder = WebApplication.CreateBuilder(args);
|
|||||||
// AddControllersWithViews includes AddControllers, so we use it for both MVC and API controllers
|
// AddControllersWithViews includes AddControllers, so we use it for both MVC and API controllers
|
||||||
builder.Services.AddControllersWithViews();
|
builder.Services.AddControllersWithViews();
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddOpenApi();
|
|
||||||
builder.Services.AddHealthChecks();
|
builder.Services.AddHealthChecks();
|
||||||
|
|
||||||
// Configure CORS - MUST be added before other services
|
// Configure CORS - MUST be added before other services
|
||||||
var corsOrigins = builder.Configuration.GetValue<string>("Cors:AllowedOrigins", "*");
|
// Configuration values come from appsettings.json (defaults) and can be overridden via environment variables
|
||||||
var corsMethods = builder.Configuration.GetValue<string>("Cors:AllowedMethods", "GET,POST,OPTIONS");
|
// Environment variables use double underscore for nested keys (e.g., CORS__ALLOWEDORIGINS)
|
||||||
var corsHeaders = builder.Configuration.GetValue<string>("Cors:AllowedHeaders", "*");
|
var corsOrigins = builder.Configuration.GetValue<string>("Cors:AllowedOrigins")
|
||||||
var corsAllowCredentials = builder.Configuration.GetValue<bool>("Cors:AllowCredentials", false);
|
?? throw new InvalidOperationException("Cors:AllowedOrigins configuration is required. Set it in appsettings.json or via CORS__ALLOWEDORIGINS environment variable.");
|
||||||
|
var corsMethods = builder.Configuration.GetValue<string>("Cors:AllowedMethods")
|
||||||
|
?? throw new InvalidOperationException("Cors:AllowedMethods configuration is required. Set it in appsettings.json or via CORS__ALLOWEDMETHODS environment variable.");
|
||||||
|
var corsHeaders = builder.Configuration.GetValue<string>("Cors:AllowedHeaders")
|
||||||
|
?? throw new InvalidOperationException("Cors:AllowedHeaders configuration is required. Set it in appsettings.json or via CORS__ALLOWEDHEADERS environment variable.");
|
||||||
|
|
||||||
// Support environment variable override (comma-separated for multiple origins)
|
// For bool, check if the key exists in configuration (GetValue<bool> returns false if not found, which is ambiguous)
|
||||||
var corsOriginsEnv = Environment.GetEnvironmentVariable("CORS__ALLOWEDORIGINS");
|
var corsAllowCredentialsKey = builder.Configuration["Cors:AllowCredentials"];
|
||||||
if (!string.IsNullOrEmpty(corsOriginsEnv))
|
if (corsAllowCredentialsKey == null)
|
||||||
{
|
{
|
||||||
corsOrigins = corsOriginsEnv;
|
throw new InvalidOperationException("Cors:AllowCredentials configuration is required. Set it in appsettings.json or via CORS__ALLOWCREDENTIALS environment variable.");
|
||||||
}
|
|
||||||
|
|
||||||
var corsMethodsEnv = Environment.GetEnvironmentVariable("CORS__ALLOWEDMETHODS");
|
|
||||||
if (!string.IsNullOrEmpty(corsMethodsEnv))
|
|
||||||
{
|
|
||||||
corsMethods = corsMethodsEnv;
|
|
||||||
}
|
|
||||||
|
|
||||||
var corsHeadersEnv = Environment.GetEnvironmentVariable("CORS__ALLOWEDHEADERS");
|
|
||||||
if (!string.IsNullOrEmpty(corsHeadersEnv))
|
|
||||||
{
|
|
||||||
corsHeaders = corsHeadersEnv;
|
|
||||||
}
|
|
||||||
|
|
||||||
var corsAllowCredentialsEnv = Environment.GetEnvironmentVariable("CORS__ALLOWCREDENTIALS");
|
|
||||||
if (!string.IsNullOrEmpty(corsAllowCredentialsEnv) && bool.TryParse(corsAllowCredentialsEnv, out var parsedCredentials))
|
|
||||||
{
|
|
||||||
corsAllowCredentials = parsedCredentials;
|
|
||||||
}
|
}
|
||||||
|
var corsAllowCredentials = builder.Configuration.GetValue<bool>("Cors:AllowCredentials");
|
||||||
|
|
||||||
builder.Services.AddCors(options =>
|
builder.Services.AddCors(options =>
|
||||||
{
|
{
|
||||||
@@ -126,23 +112,12 @@ builder.Services.AddSingleton<IBomRadarService, BomLocalService.Services.BomRada
|
|||||||
builder.Services.AddHostedService<CacheCleanupService>();
|
builder.Services.AddHostedService<CacheCleanupService>();
|
||||||
builder.Services.AddHostedService<CacheManagementService>();
|
builder.Services.AddHostedService<CacheManagementService>();
|
||||||
|
|
||||||
// Add configuration - appsettings.json provides default values
|
|
||||||
// All values can be overridden via environment variables
|
|
||||||
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
|
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// Configure the HTTP request pipeline
|
// Configure the HTTP request pipeline
|
||||||
if (app.Environment.IsDevelopment())
|
|
||||||
{
|
|
||||||
app.MapOpenApi();
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPS redirection is optional and disabled by default for Docker flexibility
|
// HTTPS redirection is optional and disabled by default for Docker flexibility
|
||||||
// Users can enable it by setting ENABLE_HTTPS_REDIRECTION=true environment variable
|
// Users can enable it by setting EnableHttpsRedirection=true in appsettings.json or ENABLEHTTPSREDIRECTION=true environment variable
|
||||||
// or EnableHttpsRedirection=true in appsettings.json
|
var enableHttpsRedirection = builder.Configuration.GetValue<bool>("EnableHttpsRedirection", false);
|
||||||
var enableHttpsRedirection = builder.Configuration.GetValue<bool>("EnableHttpsRedirection", false) ||
|
|
||||||
Environment.GetEnvironmentVariable("ENABLE_HTTPS_REDIRECTION")?.Equals("true", StringComparison.OrdinalIgnoreCase) == true;
|
|
||||||
if (enableHttpsRedirection)
|
if (enableHttpsRedirection)
|
||||||
{
|
{
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ All configuration can be done via environment variables, which override the defa
|
|||||||
|----------|-------------|---------|
|
|----------|-------------|---------|
|
||||||
| `ASPNETCORE_ENVIRONMENT` | Runtime environment (Development/Production) | `Production` |
|
| `ASPNETCORE_ENVIRONMENT` | Runtime environment (Development/Production) | `Production` |
|
||||||
| `ASPNETCORE_URLS` | URLs the service listens on | `http://+:8080` |
|
| `ASPNETCORE_URLS` | URLs the service listens on | `http://+:8080` |
|
||||||
| `ENABLE_HTTPS_REDIRECTION` | Enable HTTPS redirection | `false` |
|
| `ENABLEHTTPSREDIRECTION` | Enable HTTPS redirection | `false` |
|
||||||
|
|
||||||
#### Application Configuration
|
#### Application Configuration
|
||||||
|
|
||||||
@@ -235,8 +235,7 @@ All configuration can be done via environment variables, which override the defa
|
|||||||
|----------|-------------|---------|---------|
|
|----------|-------------|---------|---------|
|
||||||
| `CACHEMANAGEMENT__CHECKINTERVALMINUTES` | Interval between cache validity checks | `5` | `10` |
|
| `CACHEMANAGEMENT__CHECKINTERVALMINUTES` | Interval between cache validity checks | `5` | `10` |
|
||||||
| `CACHEMANAGEMENT__INITIALDELAYSECONDS` | Delay before first cache check on startup | `10` | `30` |
|
| `CACHEMANAGEMENT__INITIALDELAYSECONDS` | Delay before first cache check on startup | `10` | `30` |
|
||||||
| `CACHEMANAGEMENT__UPDATESTAGGERSECONDS` | Delay between triggering cache updates | `2` | `5` |
|
| `CACHEMANAGEMENT__LOCATIONSTAGGERSECONDS` | Delay between processing different locations (used for both initial and periodic updates) | `1` | `2` |
|
||||||
| `CACHEMANAGEMENT__LOCATIONSTAGGERSECONDS` | Delay between processing different locations | `1` | `2` |
|
|
||||||
|
|
||||||
#### Cache Cleanup
|
#### Cache Cleanup
|
||||||
|
|
||||||
@@ -880,13 +879,6 @@ DELETE /api/cache/{suburb}/{state}
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### OpenAPI Documentation
|
|
||||||
|
|
||||||
When running in Development mode, OpenAPI documentation is available at:
|
|
||||||
```
|
|
||||||
http://localhost:8082/openapi/v1.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## Cache Update Estimation
|
## Cache Update Estimation
|
||||||
|
|
||||||
The service uses a **metrics-based estimation system** to provide accurate estimates of cache update completion times. This ensures clients receive meaningful `nextUpdateTime` values that adapt to the actual hardware performance.
|
The service uses a **metrics-based estimation system** to provide accurate estimates of cache update completion times. This ensures clients receive meaningful `nextUpdateTime` values that adapt to the actual hardware performance.
|
||||||
|
|||||||
@@ -32,9 +32,27 @@ public class BomRadarService : IBomRadarService, IDisposable
|
|||||||
_scrapingService = scrapingService;
|
_scrapingService = scrapingService;
|
||||||
_debugService = debugService;
|
_debugService = debugService;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_cacheExpirationMinutes = configuration.GetValue<double>("CacheExpirationMinutes", 12.5);
|
|
||||||
_cacheManagementCheckIntervalMinutes = configuration.GetValue<int>("CacheManagement:CheckIntervalMinutes", 5);
|
var cacheExpirationMinutesConfig = configuration.GetValue<double?>("CacheExpirationMinutes");
|
||||||
_timeSeriesWarningFolderCount = configuration.GetValue<int>("TimeSeries:WarningFolderCount", 200);
|
if (!cacheExpirationMinutesConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("CacheExpirationMinutes configuration is required. Set it in appsettings.json or via CACHEEXPIRATIONMINUTES environment variable.");
|
||||||
|
}
|
||||||
|
_cacheExpirationMinutes = cacheExpirationMinutesConfig.Value;
|
||||||
|
|
||||||
|
var cacheManagementCheckIntervalMinutesConfig = configuration.GetValue<int?>("CacheManagement:CheckIntervalMinutes");
|
||||||
|
if (!cacheManagementCheckIntervalMinutesConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("CacheManagement:CheckIntervalMinutes configuration is required. Set it in appsettings.json or via CACHEMANAGEMENT__CHECKINTERVALMINUTES environment variable.");
|
||||||
|
}
|
||||||
|
_cacheManagementCheckIntervalMinutes = cacheManagementCheckIntervalMinutesConfig.Value;
|
||||||
|
|
||||||
|
var timeSeriesWarningFolderCountConfig = configuration.GetValue<int?>("TimeSeries:WarningFolderCount");
|
||||||
|
if (!timeSeriesWarningFolderCountConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("TimeSeries:WarningFolderCount configuration is required. Set it in appsettings.json or via TIMESERIES__WARNINGFOLDERCOUNT environment variable.");
|
||||||
|
}
|
||||||
|
_timeSeriesWarningFolderCount = timeSeriesWarningFolderCountConfig.Value;
|
||||||
|
|
||||||
// Calculate estimated cache update duration
|
// Calculate estimated cache update duration
|
||||||
_estimatedUpdateDurationSeconds = CacheHelper.GetEstimatedUpdateDurationSeconds(configuration, CachedDataType.Radar);
|
_estimatedUpdateDurationSeconds = CacheHelper.GetEstimatedUpdateDurationSeconds(configuration, CachedDataType.Radar);
|
||||||
|
|||||||
@@ -15,7 +15,15 @@ public class BrowserService : IBrowserService
|
|||||||
public BrowserService(ILogger<BrowserService> logger, IConfiguration configuration, IDebugService debugService)
|
public BrowserService(ILogger<BrowserService> logger, IConfiguration configuration, IDebugService debugService)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_timezone = configuration.GetValue<string>("Timezone") ?? "Australia/Brisbane";
|
// Get timezone from configuration (default from appsettings.json, can be overridden via TIMEZONE environment variable)
|
||||||
|
var timezone = configuration.GetValue<string>("Timezone");
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(timezone))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Timezone configuration is required. Set it in appsettings.json or via TIMEZONE environment variable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_timezone = timezone;
|
||||||
_debugService = debugService;
|
_debugService = debugService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,20 @@ public class CacheCleanupService : BackgroundService
|
|||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_cacheDirectory = FilePathHelper.GetCacheDirectory(configuration);
|
_cacheDirectory = FilePathHelper.GetCacheDirectory(configuration);
|
||||||
_retentionHours = configuration.GetValue<int>("CacheRetentionHours", 24);
|
|
||||||
var cleanupIntervalHours = configuration.GetValue<int>("CacheCleanup:IntervalHours", 1);
|
var retentionHoursConfig = configuration.GetValue<int?>("CacheRetentionHours");
|
||||||
|
if (!retentionHoursConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("CacheRetentionHours configuration is required. Set it in appsettings.json or via CACHERETENTIONHOURS environment variable.");
|
||||||
|
}
|
||||||
|
_retentionHours = retentionHoursConfig.Value;
|
||||||
|
|
||||||
|
var cleanupIntervalHoursConfig = configuration.GetValue<int?>("CacheCleanup:IntervalHours");
|
||||||
|
if (!cleanupIntervalHoursConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("CacheCleanup:IntervalHours configuration is required. Set it in appsettings.json or via CACHECLEANUP__INTERVALHOURS environment variable.");
|
||||||
|
}
|
||||||
|
var cleanupIntervalHours = cleanupIntervalHoursConfig.Value;
|
||||||
_cleanupInterval = TimeSpan.FromHours(cleanupIntervalHours);
|
_cleanupInterval = TimeSpan.FromHours(cleanupIntervalHours);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ public class CacheManagementService : BackgroundService
|
|||||||
private readonly IBrowserService _browserService;
|
private readonly IBrowserService _browserService;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
private readonly TimeSpan _checkInterval;
|
private readonly TimeSpan _checkInterval;
|
||||||
|
private readonly TimeSpan _locationStaggerInterval;
|
||||||
|
private readonly TimeSpan _initialDelayInterval;
|
||||||
private readonly HashSet<string> _activeUpdates = new();
|
private readonly HashSet<string> _activeUpdates = new();
|
||||||
private readonly object _lock = new();
|
private readonly object _lock = new();
|
||||||
|
|
||||||
@@ -24,14 +26,34 @@ public class CacheManagementService : BackgroundService
|
|||||||
_bomRadarService = bomRadarService;
|
_bomRadarService = bomRadarService;
|
||||||
_browserService = browserService;
|
_browserService = browserService;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
var checkIntervalMinutes = configuration.GetValue<int>("CacheManagement:CheckIntervalMinutes", 5);
|
|
||||||
|
var checkIntervalMinutesConfig = configuration.GetValue<int?>("CacheManagement:CheckIntervalMinutes");
|
||||||
|
if (!checkIntervalMinutesConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("CacheManagement:CheckIntervalMinutes configuration is required. Set it in appsettings.json or via CACHEMANAGEMENT__CHECKINTERVALMINUTES environment variable.");
|
||||||
|
}
|
||||||
|
var checkIntervalMinutes = checkIntervalMinutesConfig.Value;
|
||||||
|
|
||||||
if (checkIntervalMinutes <= 0 || checkIntervalMinutes > 60)
|
if (checkIntervalMinutes <= 0 || checkIntervalMinutes > 60)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("CacheManagement:CheckIntervalMinutes must be between 1 and 60", nameof(configuration));
|
throw new ArgumentException("CacheManagement:CheckIntervalMinutes must be between 1 and 60", nameof(configuration));
|
||||||
}
|
}
|
||||||
|
|
||||||
_checkInterval = TimeSpan.FromMinutes(checkIntervalMinutes);
|
_checkInterval = TimeSpan.FromMinutes(checkIntervalMinutes);
|
||||||
|
|
||||||
|
// LocationStaggerSeconds is used for delays between processing locations (both initial and periodic)
|
||||||
|
var locationStaggerSecondsConfig = configuration.GetValue<int?>("CacheManagement:LocationStaggerSeconds");
|
||||||
|
if (!locationStaggerSecondsConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("CacheManagement:LocationStaggerSeconds configuration is required. Set it in appsettings.json or via CACHEMANAGEMENT__LOCATIONSTAGGERSECONDS environment variable.");
|
||||||
|
}
|
||||||
|
_locationStaggerInterval = TimeSpan.FromSeconds(locationStaggerSecondsConfig.Value);
|
||||||
|
|
||||||
|
var initialDelaySecondsConfig = configuration.GetValue<int?>("CacheManagement:InitialDelaySeconds");
|
||||||
|
if (!initialDelaySecondsConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("CacheManagement:InitialDelaySeconds configuration is required. Set it in appsettings.json or via CACHEMANAGEMENT__INITIALDELAYSECONDS environment variable.");
|
||||||
|
}
|
||||||
|
_initialDelayInterval = TimeSpan.FromSeconds(initialDelaySecondsConfig.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
@@ -39,8 +61,7 @@ public class CacheManagementService : BackgroundService
|
|||||||
_logger.LogInformation("Cache management service started. Check interval: {Interval}", _checkInterval);
|
_logger.LogInformation("Cache management service started. Check interval: {Interval}", _checkInterval);
|
||||||
|
|
||||||
// Wait a bit for the service to fully initialize
|
// Wait a bit for the service to fully initialize
|
||||||
var initialDelaySeconds = _configuration.GetValue<int>("CacheManagement:InitialDelaySeconds", 10);
|
await Task.Delay(_initialDelayInterval, stoppingToken);
|
||||||
await Task.Delay(TimeSpan.FromSeconds(initialDelaySeconds), stoppingToken);
|
|
||||||
|
|
||||||
// Pre-warm the browser before starting cache updates
|
// Pre-warm the browser before starting cache updates
|
||||||
_logger.LogInformation("Pre-warming browser before cache updates");
|
_logger.LogInformation("Pre-warming browser before cache updates");
|
||||||
@@ -58,28 +79,7 @@ public class CacheManagementService : BackgroundService
|
|||||||
|
|
||||||
// Initial cache update on startup
|
// Initial cache update on startup
|
||||||
_logger.LogInformation("Performing initial cache update for {Count} locations", locationsToManage.Count);
|
_logger.LogInformation("Performing initial cache update for {Count} locations", locationsToManage.Count);
|
||||||
int initialUpdatesTriggered = 0;
|
var (initialUpdatesTriggered, initialCachesValid) = await ProcessLocationsAsync(locationsToManage, stoppingToken);
|
||||||
int initialCachesValid = 0;
|
|
||||||
|
|
||||||
foreach (var (suburb, state) in locationsToManage)
|
|
||||||
{
|
|
||||||
if (stoppingToken.IsCancellationRequested)
|
|
||||||
break;
|
|
||||||
|
|
||||||
var (triggered, isValid) = await UpdateCacheIfNeededAsync(suburb, state, stoppingToken);
|
|
||||||
if (triggered)
|
|
||||||
{
|
|
||||||
initialUpdatesTriggered++;
|
|
||||||
}
|
|
||||||
else if (isValid)
|
|
||||||
{
|
|
||||||
initialCachesValid++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stagger updates to avoid overwhelming the system
|
|
||||||
var updateStaggerSeconds = _configuration.GetValue<int>("CacheManagement:UpdateStaggerSeconds", 2);
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(updateStaggerSeconds), stoppingToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Initial cache update completed: {UpdatesTriggered} updates triggered, {CachesValid} caches valid",
|
_logger.LogInformation("Initial cache update completed: {UpdatesTriggered} updates triggered, {CachesValid} caches valid",
|
||||||
initialUpdatesTriggered, initialCachesValid);
|
initialUpdatesTriggered, initialCachesValid);
|
||||||
@@ -95,28 +95,7 @@ public class CacheManagementService : BackgroundService
|
|||||||
locationsToManage = GetLocationsToManage();
|
locationsToManage = GetLocationsToManage();
|
||||||
_logger.LogInformation("Starting periodic cache check for {Count} locations", locationsToManage.Count);
|
_logger.LogInformation("Starting periodic cache check for {Count} locations", locationsToManage.Count);
|
||||||
|
|
||||||
int updatesTriggered = 0;
|
var (updatesTriggered, cachesValid) = await ProcessLocationsAsync(locationsToManage, stoppingToken);
|
||||||
int cachesValid = 0;
|
|
||||||
|
|
||||||
foreach (var (suburb, state) in locationsToManage)
|
|
||||||
{
|
|
||||||
if (stoppingToken.IsCancellationRequested)
|
|
||||||
break;
|
|
||||||
|
|
||||||
var (triggered, isValid) = await UpdateCacheIfNeededAsync(suburb, state, stoppingToken);
|
|
||||||
if (triggered)
|
|
||||||
{
|
|
||||||
updatesTriggered++;
|
|
||||||
}
|
|
||||||
else if (isValid)
|
|
||||||
{
|
|
||||||
cachesValid++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Small delay between locations
|
|
||||||
var locationStaggerSeconds = _configuration.GetValue<int>("CacheManagement:LocationStaggerSeconds", 1);
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(locationStaggerSeconds), stoppingToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Periodic cache check completed: {UpdatesTriggered} updates triggered, {CachesValid} caches valid",
|
_logger.LogInformation("Periodic cache check completed: {UpdatesTriggered} updates triggered, {CachesValid} caches valid",
|
||||||
updatesTriggered, cachesValid);
|
updatesTriggered, cachesValid);
|
||||||
@@ -128,6 +107,38 @@ public class CacheManagementService : BackgroundService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes a list of locations, checking and updating cache as needed
|
||||||
|
/// </summary>
|
||||||
|
private async Task<(int updatesTriggered, int cachesValid)> ProcessLocationsAsync(
|
||||||
|
List<(string suburb, string state)> locations,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
int updatesTriggered = 0;
|
||||||
|
int cachesValid = 0;
|
||||||
|
|
||||||
|
foreach (var (suburb, state) in locations)
|
||||||
|
{
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var (triggered, isValid) = await UpdateCacheIfNeededAsync(suburb, state, cancellationToken);
|
||||||
|
if (triggered)
|
||||||
|
{
|
||||||
|
updatesTriggered++;
|
||||||
|
}
|
||||||
|
else if (isValid)
|
||||||
|
{
|
||||||
|
cachesValid++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stagger processing to avoid overwhelming the system
|
||||||
|
await Task.Delay(_locationStaggerInterval, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (updatesTriggered, cachesValid);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<(bool updateTriggered, bool isValid)> UpdateCacheIfNeededAsync(string suburb, string state, CancellationToken cancellationToken)
|
private async Task<(bool updateTriggered, bool isValid)> UpdateCacheIfNeededAsync(string suburb, string state, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var locationKey = LocationHelper.GetLocationKey(suburb, state);
|
var locationKey = LocationHelper.GetLocationKey(suburb, state);
|
||||||
|
|||||||
@@ -27,7 +27,13 @@ public class CacheService : ICacheService
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_cacheDirectory = FilePathHelper.GetCacheDirectory(configuration);
|
_cacheDirectory = FilePathHelper.GetCacheDirectory(configuration);
|
||||||
_cacheExpirationMinutes = configuration.GetValue<double>("CacheExpirationMinutes", 12.5);
|
|
||||||
|
var cacheExpirationMinutesConfig = configuration.GetValue<double?>("CacheExpirationMinutes");
|
||||||
|
if (!cacheExpirationMinutesConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("CacheExpirationMinutes configuration is required. Set it in appsettings.json or via CACHEEXPIRATIONMINUTES environment variable.");
|
||||||
|
}
|
||||||
|
_cacheExpirationMinutes = cacheExpirationMinutesConfig.Value;
|
||||||
|
|
||||||
if (_cacheExpirationMinutes <= 0)
|
if (_cacheExpirationMinutes <= 0)
|
||||||
{
|
{
|
||||||
|
|||||||
+10
-13
@@ -14,20 +14,17 @@ namespace BomLocalService.Services;
|
|||||||
public DebugService(ILogger<DebugService> logger, IConfiguration configuration)
|
public DebugService(ILogger<DebugService> logger, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_waitMs = configuration.GetValue<int>("Debug:WaitMs", 2000);
|
|
||||||
|
var waitMsConfig = configuration.GetValue<int?>("Debug:WaitMs");
|
||||||
|
if (!waitMsConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Debug:WaitMs configuration is required. Set it in appsettings.json or via DEBUG__WAITMS environment variable.");
|
||||||
|
}
|
||||||
|
_waitMs = waitMsConfig.Value;
|
||||||
|
|
||||||
// Check all possible ways the config might be set
|
// Get debug enabled from configuration (default from appsettings.json, can be overridden via DEBUG__ENABLED environment variable)
|
||||||
var debugEnabledEnv = Environment.GetEnvironmentVariable("DEBUG__ENABLED");
|
// Note: bool defaults to false if not found, which is acceptable for Debug:Enabled
|
||||||
var debugEnabledConfig = configuration.GetValue<bool>("Debug:Enabled", false);
|
_enabled = configuration.GetValue<bool>("Debug:Enabled", false);
|
||||||
var debugEnabledFromConfig = configuration["Debug:Enabled"];
|
|
||||||
|
|
||||||
_logger.LogInformation("Debug configuration check - Env var DEBUG__ENABLED: {EnvVar}, Config Debug:Enabled: {ConfigValue}, Config string: {ConfigString}",
|
|
||||||
debugEnabledEnv ?? "null", debugEnabledConfig, debugEnabledFromConfig ?? "null");
|
|
||||||
|
|
||||||
// Try environment variable first, then config
|
|
||||||
_enabled = !string.IsNullOrEmpty(debugEnabledEnv)
|
|
||||||
? bool.TryParse(debugEnabledEnv, out var envBool) && envBool
|
|
||||||
: debugEnabledConfig;
|
|
||||||
|
|
||||||
var cacheDirectory = FilePathHelper.GetCacheDirectory(configuration);
|
var cacheDirectory = FilePathHelper.GetCacheDirectory(configuration);
|
||||||
_debugDirectory = Path.Combine(cacheDirectory, "debug");
|
_debugDirectory = Path.Combine(cacheDirectory, "debug");
|
||||||
|
|||||||
@@ -25,15 +25,38 @@ public class CaptureFramesStep : BaseScrapingStep
|
|||||||
: base(logger, selectorService, debugService, configuration)
|
: base(logger, selectorService, debugService, configuration)
|
||||||
{
|
{
|
||||||
_cacheService = cacheService;
|
_cacheService = cacheService;
|
||||||
_tileRenderWaitMs = configuration.GetValue<int>("Screenshot:TileRenderWaitMs", 5000);
|
|
||||||
|
var tileRenderWaitMsConfig = configuration.GetValue<int?>("Screenshot:TileRenderWaitMs");
|
||||||
|
if (!tileRenderWaitMsConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Screenshot:TileRenderWaitMs configuration is required. Set it in appsettings.json or via SCREENSHOT__TILERENDERWAITMS environment variable.");
|
||||||
|
}
|
||||||
|
_tileRenderWaitMs = tileRenderWaitMsConfig.Value;
|
||||||
|
|
||||||
var cropSection = configuration.GetSection("Screenshot:Crop");
|
var cropSection = configuration.GetSection("Screenshot:Crop");
|
||||||
|
|
||||||
|
var cropXConfig = cropSection.GetValue<int?>("X");
|
||||||
|
if (!cropXConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Screenshot:Crop:X configuration is required. Set it in appsettings.json or via SCREENSHOT__CROP__X environment variable.");
|
||||||
|
}
|
||||||
|
var cropYConfig = cropSection.GetValue<int?>("Y");
|
||||||
|
if (!cropYConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Screenshot:Crop:Y configuration is required. Set it in appsettings.json or via SCREENSHOT__CROP__Y environment variable.");
|
||||||
|
}
|
||||||
|
var cropRightOffsetConfig = cropSection.GetValue<int?>("RightOffset");
|
||||||
|
if (!cropRightOffsetConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Screenshot:Crop:RightOffset configuration is required. Set it in appsettings.json or via SCREENSHOT__CROP__RIGHTOFFSET environment variable.");
|
||||||
|
}
|
||||||
|
|
||||||
_cropConfig = new ScreenshotCropConfig
|
_cropConfig = new ScreenshotCropConfig
|
||||||
{
|
{
|
||||||
X = cropSection.GetValue<int>("X", 0),
|
X = cropXConfig.Value,
|
||||||
Y = cropSection.GetValue<int>("Y", 0),
|
Y = cropYConfig.Value,
|
||||||
RightOffset = cropSection.GetValue<int>("RightOffset", 0),
|
RightOffset = cropRightOffsetConfig.Value,
|
||||||
Height = cropSection.GetValue<int?>("Height")
|
Height = cropSection.GetValue<int?>("Height") // Height is optional (nullable)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,12 @@ public class ResetToFirstFrameStep : BaseScrapingStep
|
|||||||
await SaveDebugAsync(context, 10, "scrubber_at_position_0", cancellationToken);
|
await SaveDebugAsync(context, 10, "scrubber_at_position_0", cancellationToken);
|
||||||
|
|
||||||
// Wait for frame 0 tiles to fully render
|
// Wait for frame 0 tiles to fully render
|
||||||
var tileRenderWaitMs = Configuration.GetValue<int>("Screenshot:TileRenderWaitMs", 5000);
|
var tileRenderWaitMsConfig = Configuration.GetValue<int?>("Screenshot:TileRenderWaitMs");
|
||||||
|
if (!tileRenderWaitMsConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Screenshot:TileRenderWaitMs configuration is required. Set it in appsettings.json or via SCREENSHOT__TILERENDERWAITMS environment variable.");
|
||||||
|
}
|
||||||
|
var tileRenderWaitMs = tileRenderWaitMsConfig.Value;
|
||||||
await context.Page.WaitForTimeoutAsync(tileRenderWaitMs);
|
await context.Page.WaitForTimeoutAsync(tileRenderWaitMs);
|
||||||
|
|
||||||
context.CurrentState = PageState.Frame0Selected;
|
context.CurrentState = PageState.Frame0Selected;
|
||||||
|
|||||||
@@ -18,7 +18,12 @@ public class WaitForMapReadyStep : BaseScrapingStep
|
|||||||
IConfiguration configuration)
|
IConfiguration configuration)
|
||||||
: base(logger, selectorService, debugService, configuration)
|
: base(logger, selectorService, debugService, configuration)
|
||||||
{
|
{
|
||||||
_tileRenderWaitMs = configuration.GetValue<int>("Screenshot:TileRenderWaitMs", 5000);
|
var tileRenderWaitMsConfig = configuration.GetValue<int?>("Screenshot:TileRenderWaitMs");
|
||||||
|
if (!tileRenderWaitMsConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Screenshot:TileRenderWaitMs configuration is required. Set it in appsettings.json or via SCREENSHOT__TILERENDERWAITMS environment variable.");
|
||||||
|
}
|
||||||
|
_tileRenderWaitMs = tileRenderWaitMsConfig.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanExecute(ScrapingContext context)
|
public override bool CanExecute(ScrapingContext context)
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ public class NavigateHomepageStep : BaseScrapingStep
|
|||||||
IConfiguration configuration)
|
IConfiguration configuration)
|
||||||
: base(logger, selectorService, debugService, configuration)
|
: base(logger, selectorService, debugService, configuration)
|
||||||
{
|
{
|
||||||
_baseUrl = configuration.GetValue<string>("Scraping:BaseUrl") ?? "https://www.bom.gov.au/";
|
_baseUrl = configuration.GetValue<string>("Scraping:BaseUrl")
|
||||||
|
?? throw new InvalidOperationException("Scraping:BaseUrl configuration is required. Set it in appsettings.json or via SCRAPING__BASEURL environment variable.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanExecute(ScrapingContext context)
|
public override bool CanExecute(ScrapingContext context)
|
||||||
|
|||||||
@@ -174,7 +174,12 @@ public class SelectSearchResultStep : BaseScrapingStep
|
|||||||
|
|
||||||
// Wait for forecast page to load
|
// Wait for forecast page to load
|
||||||
await context.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded, new PageWaitForLoadStateOptions { Timeout = 15000 });
|
await context.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded, new PageWaitForLoadStateOptions { Timeout = 15000 });
|
||||||
var dynamicContentWaitMs = Configuration.GetValue<int>("Screenshot:DynamicContentWaitMs", 2000);
|
var dynamicContentWaitMsConfig = Configuration.GetValue<int?>("Screenshot:DynamicContentWaitMs");
|
||||||
|
if (!dynamicContentWaitMsConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Screenshot:DynamicContentWaitMs configuration is required. Set it in appsettings.json or via SCREENSHOT__DYNAMICCONTENTWAITMS environment variable.");
|
||||||
|
}
|
||||||
|
var dynamicContentWaitMs = dynamicContentWaitMsConfig.Value;
|
||||||
await context.Page.WaitForTimeoutAsync(dynamicContentWaitMs);
|
await context.Page.WaitForTimeoutAsync(dynamicContentWaitMs);
|
||||||
await SaveDebugAsync(context, 5, "forecast_page_loaded", cancellationToken);
|
await SaveDebugAsync(context, 5, "forecast_page_loaded", cancellationToken);
|
||||||
|
|
||||||
|
|||||||
@@ -44,8 +44,20 @@ public class RadarScrapingWorkflow : IWorkflow<RadarResponse>
|
|||||||
_stepRegistry = stepRegistry;
|
_stepRegistry = stepRegistry;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_cacheService = cacheService;
|
_cacheService = cacheService;
|
||||||
_cacheExpirationMinutes = configuration.GetValue<double>("CacheExpirationMinutes", 12.5);
|
|
||||||
_cacheManagementCheckIntervalMinutes = configuration.GetValue<int>("CacheManagement:CheckIntervalMinutes", 5);
|
var cacheExpirationMinutesConfig = configuration.GetValue<double?>("CacheExpirationMinutes");
|
||||||
|
if (!cacheExpirationMinutesConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("CacheExpirationMinutes configuration is required. Set it in appsettings.json or via CACHEEXPIRATIONMINUTES environment variable.");
|
||||||
|
}
|
||||||
|
_cacheExpirationMinutes = cacheExpirationMinutesConfig.Value;
|
||||||
|
|
||||||
|
var cacheManagementCheckIntervalMinutesConfig = configuration.GetValue<int?>("CacheManagement:CheckIntervalMinutes");
|
||||||
|
if (!cacheManagementCheckIntervalMinutesConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("CacheManagement:CheckIntervalMinutes configuration is required. Set it in appsettings.json or via CACHEMANAGEMENT__CHECKINTERVALMINUTES environment variable.");
|
||||||
|
}
|
||||||
|
_cacheManagementCheckIntervalMinutes = cacheManagementCheckIntervalMinutesConfig.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RadarResponse> ExecuteAsync(ScrapingContext context, CancellationToken cancellationToken)
|
public async Task<RadarResponse> ExecuteAsync(ScrapingContext context, CancellationToken cancellationToken)
|
||||||
|
|||||||
@@ -14,7 +14,13 @@ public class TimeParsingService : ITimeParsingService
|
|||||||
public TimeParsingService(ILogger<TimeParsingService> logger, IConfiguration configuration)
|
public TimeParsingService(ILogger<TimeParsingService> logger, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
var timezone = configuration.GetValue<string>("Timezone") ?? "Australia/Brisbane";
|
// Get timezone from configuration (default from appsettings.json, can be overridden via TIMEZONE environment variable)
|
||||||
|
var timezone = configuration.GetValue<string>("Timezone");
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(timezone))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Timezone configuration is required. Set it in appsettings.json or via TIMEZONE environment variable.");
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -23,8 +29,8 @@ public class TimeParsingService : ITimeParsingService
|
|||||||
}
|
}
|
||||||
catch (TimeZoneNotFoundException)
|
catch (TimeZoneNotFoundException)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Timezone {Timezone} not found, defaulting to Australia/Brisbane", timezone);
|
_logger.LogError("Timezone {Timezone} not found. Please use a valid IANA timezone identifier.", timezone);
|
||||||
_timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Australia/Brisbane");
|
throw new InvalidOperationException($"Invalid timezone '{timezone}'. Please use a valid IANA timezone identifier (e.g., Australia/Sydney, Australia/Brisbane).");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,12 @@ public static class CacheHelper
|
|||||||
public static int GetFrameCountForDataType(IConfiguration configuration, CachedDataType dataType)
|
public static int GetFrameCountForDataType(IConfiguration configuration, CachedDataType dataType)
|
||||||
{
|
{
|
||||||
var dataTypeName = dataType.ToString();
|
var dataTypeName = dataType.ToString();
|
||||||
var frameCount = configuration.GetValue<int>($"CachedDataTypes:{dataTypeName}:FrameCount", 7);
|
var frameCountConfig = configuration.GetValue<int?>($"CachedDataTypes:{dataTypeName}:FrameCount");
|
||||||
return frameCount;
|
if (!frameCountConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"CachedDataTypes:{dataTypeName}:FrameCount configuration is required. Set it in appsettings.json or via CACHEDDATATYPES__{dataTypeName.ToUpperInvariant()}__FRAMECOUNT environment variable.");
|
||||||
|
}
|
||||||
|
return frameCountConfig.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -71,8 +75,20 @@ public static class CacheHelper
|
|||||||
{
|
{
|
||||||
// Calculate based on actual wait times and frame count
|
// Calculate based on actual wait times and frame count
|
||||||
var frameCount = GetFrameCountForDataType(configuration, dataType);
|
var frameCount = GetFrameCountForDataType(configuration, dataType);
|
||||||
var tileRenderWaitMs = configuration.GetValue<int>("Screenshot:TileRenderWaitMs", 5000);
|
|
||||||
var dynamicContentWaitMs = configuration.GetValue<int>("Screenshot:DynamicContentWaitMs", 2000);
|
var tileRenderWaitMsConfig = configuration.GetValue<int?>("Screenshot:TileRenderWaitMs");
|
||||||
|
if (!tileRenderWaitMsConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Screenshot:TileRenderWaitMs configuration is required. Set it in appsettings.json or via SCREENSHOT__TILERENDERWAITMS environment variable.");
|
||||||
|
}
|
||||||
|
var tileRenderWaitMs = tileRenderWaitMsConfig.Value;
|
||||||
|
|
||||||
|
var dynamicContentWaitMsConfig = configuration.GetValue<int?>("Screenshot:DynamicContentWaitMs");
|
||||||
|
if (!dynamicContentWaitMsConfig.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Screenshot:DynamicContentWaitMs configuration is required. Set it in appsettings.json or via SCREENSHOT__DYNAMICCONTENTWAITMS environment variable.");
|
||||||
|
}
|
||||||
|
var dynamicContentWaitMs = dynamicContentWaitMsConfig.Value;
|
||||||
|
|
||||||
// Rough calculation:
|
// Rough calculation:
|
||||||
// - Initial page load and navigation: ~10-15 seconds
|
// - Initial page load and navigation: ~10-15 seconds
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ namespace BomLocalService.Utilities;
|
|||||||
public static class FilePathHelper
|
public static class FilePathHelper
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the cache directory path from configuration or returns default
|
/// Gets the cache directory path from configuration (default from appsettings.json, can be overridden via CACHEDIRECTORY environment variable)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string GetCacheDirectory(IConfiguration configuration)
|
public static string GetCacheDirectory(IConfiguration configuration)
|
||||||
{
|
{
|
||||||
return configuration.GetValue<string>("CacheDirectory")
|
return configuration.GetValue<string>("CacheDirectory")
|
||||||
?? Path.Combine(AppContext.BaseDirectory, "cache");
|
?? throw new InvalidOperationException("CacheDirectory configuration is required. Set it in appsettings.json or via CACHEDIRECTORY environment variable.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
"CacheManagement": {
|
"CacheManagement": {
|
||||||
"CheckIntervalMinutes": 5,
|
"CheckIntervalMinutes": 5,
|
||||||
"InitialDelaySeconds": 10,
|
"InitialDelaySeconds": 10,
|
||||||
"UpdateStaggerSeconds": 2,
|
|
||||||
"LocationStaggerSeconds": 1
|
"LocationStaggerSeconds": 1
|
||||||
},
|
},
|
||||||
"CacheCleanup": {
|
"CacheCleanup": {
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ services:
|
|||||||
- CACHEEXPIRATIONMINUTES=${CACHEEXPIRATIONMINUTES:-12.5}
|
- CACHEEXPIRATIONMINUTES=${CACHEEXPIRATIONMINUTES:-12.5}
|
||||||
- CACHEMANAGEMENT__CHECKINTERVALMINUTES=${CACHEMANAGEMENT__CHECKINTERVALMINUTES:-5}
|
- CACHEMANAGEMENT__CHECKINTERVALMINUTES=${CACHEMANAGEMENT__CHECKINTERVALMINUTES:-5}
|
||||||
- CACHEMANAGEMENT__INITIALDELAYSECONDS=${CACHEMANAGEMENT__INITIALDELAYSECONDS:-10}
|
- CACHEMANAGEMENT__INITIALDELAYSECONDS=${CACHEMANAGEMENT__INITIALDELAYSECONDS:-10}
|
||||||
- CACHEMANAGEMENT__UPDATESTAGGERSECONDS=${CACHEMANAGEMENT__UPDATESTAGGERSECONDS:-2}
|
|
||||||
- CACHEMANAGEMENT__LOCATIONSTAGGERSECONDS=${CACHEMANAGEMENT__LOCATIONSTAGGERSECONDS:-1}
|
- CACHEMANAGEMENT__LOCATIONSTAGGERSECONDS=${CACHEMANAGEMENT__LOCATIONSTAGGERSECONDS:-1}
|
||||||
- CACHECLEANUP__INTERVALHOURS=${CACHECLEANUP__INTERVALHOURS:-1}
|
- CACHECLEANUP__INTERVALHOURS=${CACHECLEANUP__INTERVALHOURS:-1}
|
||||||
- SCREENSHOT__DYNAMICCONTENTWAITMS=${SCREENSHOT__DYNAMICCONTENTWAITMS:-2000}
|
- SCREENSHOT__DYNAMICCONTENTWAITMS=${SCREENSHOT__DYNAMICCONTENTWAITMS:-2000}
|
||||||
|
|||||||
Reference in New Issue
Block a user