Fix: Exclude active cache folders in GetCachedFrameAsync to prevent reading incomplete data

- Add active cache folder exclusion to GetCachedFrameAsync
- Prevents TaskCanceledException when reading metadata from folders being written to
- Matches pattern used in GetCachedRadarAsync for consistency
This commit is contained in:
2025-12-16 01:57:02 +10:00
parent e5aee5be82
commit ee5bffde88
4 changed files with 92 additions and 1 deletions
+74
View File
@@ -9,6 +9,74 @@ builder.Services.AddControllersWithViews();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApi();
// Configure CORS - MUST be added before other services
var corsOrigins = builder.Configuration.GetValue<string>("Cors:AllowedOrigins", "*");
var corsMethods = builder.Configuration.GetValue<string>("Cors:AllowedMethods", "GET,POST,OPTIONS");
var corsHeaders = builder.Configuration.GetValue<string>("Cors:AllowedHeaders", "*");
var corsAllowCredentials = builder.Configuration.GetValue<bool>("Cors:AllowCredentials", false);
// Support environment variable override (comma-separated for multiple origins)
var corsOriginsEnv = Environment.GetEnvironmentVariable("CORS__ALLOWEDORIGINS");
if (!string.IsNullOrEmpty(corsOriginsEnv))
{
corsOrigins = corsOriginsEnv;
}
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;
}
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
if (corsOrigins == "*")
{
policy.AllowAnyOrigin();
}
else
{
// Split comma-separated origins
var origins = corsOrigins.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
policy.WithOrigins(origins);
}
// Split comma-separated methods
var methods = corsMethods.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
policy.WithMethods(methods);
// Split comma-separated headers or allow all
if (corsHeaders == "*")
{
policy.AllowAnyHeader();
}
else
{
var headers = corsHeaders.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
policy.WithHeaders(headers);
}
if (corsAllowCredentials)
{
policy.AllowCredentials();
}
});
});
// Register core services via interfaces (order matters - dependencies must be registered first)
builder.Services.AddSingleton<IDebugService, DebugService>();
builder.Services.AddSingleton<ICacheService, CacheService>();
@@ -45,6 +113,12 @@ if (enableHttpsRedirection)
app.UseHttpsRedirection();
}
// Enable routing (required for CORS to work with controllers)
app.UseRouting();
// CORS middleware - MUST be after UseRouting but before MapControllers
app.UseCors();
// No authorization required - service is designed to run behind a reverse proxy if auth is needed
// Map MVC routes first (before API routes to avoid conflicts)
+4 -1
View File
@@ -116,7 +116,10 @@ public class CacheService : ICacheService
return null;
}
var (cacheFolderPath, _) = await GetCachedScreenshotWithMetadataAsync(suburb, state, CachedDataType.Radar, null, cancellationToken);
// Exclude active cache folder (currently being written to) to avoid reading incomplete data
var locationKey = LocationHelper.GetLocationKey(suburb, state);
var excludeFolder = GetActiveCacheFolder(locationKey);
var (cacheFolderPath, _) = await GetCachedScreenshotWithMetadataAsync(suburb, state, CachedDataType.Radar, excludeFolder, cancellationToken);
if (string.IsNullOrEmpty(cacheFolderPath))
{
+6
View File
@@ -6,6 +6,12 @@
}
},
"AllowedHosts": "*",
"Cors": {
"AllowedOrigins": "*",
"AllowedMethods": "GET,POST,OPTIONS",
"AllowedHeaders": "*",
"AllowCredentials": false
},
"CacheDirectory": "./cache",
"CacheRetentionHours": 24,
"CacheExpirationMinutes": 12.5,
+8
View File
@@ -17,6 +17,14 @@ services:
# Virtual display for non-headless browser
- DISPLAY=:99
# CORS Configuration (for Home Assistant integration)
# Multiple origins can be comma-separated: "http://homeassistant.local:8123,http://192.168.1.100:8123"
# Use "*" to allow all origins (less secure, but convenient for development)
- CORS__ALLOWEDORIGINS=${CORS__ALLOWEDORIGINS:-*}
- CORS__ALLOWEDMETHODS=${CORS__ALLOWEDMETHODS:-GET,POST,OPTIONS}
- CORS__ALLOWEDHEADERS=${CORS__ALLOWEDHEADERS:-*}
- CORS__ALLOWCREDENTIALS=${CORS__ALLOWCREDENTIALS:-false}
# Application configuration (override appsettings.json)
# Use double underscore (__) for nested config keys
- CACHEDIRECTORY=${CACHEDIRECTORY:-/app/cache}