Files
alex ffd3636d1c Remove minutes ago parsing, use UTC timestamps directly
- Refactor TimeParsingService to parse time strings to UTC only
- Remove minutesAgo from RadarFrame and FrameMetadata models
- Update CaptureFramesStep to extract UTC timestamps directly
- Update CacheService to store/load ObservationTime instead of MinutesAgo
- Update BomRadarService to use AbsoluteObservationTime directly
- Remove all backward compatibility code for old cache folders
- Update documentation and test SPA to calculate minutes ago on client
- Fix step registration to register concrete types for DI resolution
- Fix hosted service registration to use reflection correctly
2025-12-23 21:52:42 +10:00

170 lines
5.9 KiB
C#

using BomLocalService.Services.Interfaces.Registration;
using BomLocalService.Utilities;
using Microsoft.Extensions.Hosting;
namespace BomLocalService.Services;
public class CacheCleanupService : BackgroundService, IHostedServiceRegistration
{
private readonly ILogger<CacheCleanupService> _logger;
private readonly string _cacheDirectory;
private readonly int _retentionHours;
private readonly TimeSpan _cleanupInterval;
public CacheCleanupService(ILogger<CacheCleanupService> logger, IConfiguration configuration)
{
_logger = logger;
_cacheDirectory = FilePathHelper.GetCacheDirectory(configuration);
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);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Cache cleanup service started. Retention: {RetentionHours} hours, Interval: {Interval}",
_retentionHours, _cleanupInterval);
while (!stoppingToken.IsCancellationRequested)
{
try
{
await CleanupOldCacheFilesAsync(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during cache cleanup");
}
await Task.Delay(_cleanupInterval, stoppingToken);
}
}
private Task CleanupOldCacheFilesAsync(CancellationToken cancellationToken)
{
if (!Directory.Exists(_cacheDirectory))
{
return Task.CompletedTask;
}
var cutoffTime = DateTime.UtcNow.AddHours(-_retentionHours);
var deletedCount = 0;
var totalSize = 0L;
try
{
// Get all cache folders
var folders = Directory.GetDirectories(_cacheDirectory);
foreach (var folder in folders)
{
if (cancellationToken.IsCancellationRequested)
break;
try
{
var folderInfo = new DirectoryInfo(folder);
var folderTime = folderInfo.LastWriteTimeUtc;
// Delete if folder is older than retention period
if (folderTime < cutoffTime)
{
var folderSize = GetFolderSize(folder);
Directory.Delete(folder, recursive: true);
deletedCount++;
totalSize += folderSize;
_logger.LogDebug("Deleted old cache folder: {Folder} (age: {Age})",
Path.GetFileName(folder), DateTime.UtcNow - folderTime);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to delete cache folder: {Folder}", folder);
}
}
if (deletedCount > 0)
{
var sizeInMB = totalSize / (1024.0 * 1024.0);
_logger.LogInformation("Cache cleanup completed. Deleted {Count} folders ({Size:F2} MB)",
deletedCount, sizeInMB);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during cache folder enumeration");
}
// Also clean up old debug directories
try
{
var debugDir = Path.Combine(_cacheDirectory, "debug");
if (Directory.Exists(debugDir))
{
var debugDirs = Directory.GetDirectories(debugDir);
foreach (var dir in debugDirs)
{
if (cancellationToken.IsCancellationRequested)
break;
try
{
var dirInfo = new DirectoryInfo(dir);
if (dirInfo.LastWriteTimeUtc < cutoffTime)
{
Directory.Delete(dir, recursive: true);
_logger.LogDebug("Deleted old debug directory: {Dir}", dir);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to delete debug directory: {Dir}", dir);
}
}
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Error cleaning up debug directories: {Error}", ex.Message);
}
return Task.CompletedTask;
}
/// <summary>
/// Calculates the total size of a folder and all its contents
/// </summary>
private long GetFolderSize(string folderPath)
{
long size = 0;
try
{
var files = Directory.GetFiles(folderPath, "*", SearchOption.AllDirectories);
foreach (var file in files)
{
var fileInfo = new FileInfo(file);
size += fileInfo.Length;
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Error calculating folder size for: {Folder}", folderPath);
}
return size;
}
}