diff --git a/CHANGELOG.md b/CHANGELOG.md index 16c46c2e4..bdfc629d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - This is most often seen with VAAPI acceleration (radeonsi or i965 drivers) - Add API endpoint to clean artwork cache folder (on demand) - POST `/api/maintenance/clean_artwork` +- Add health check to warn about unsupported empty (classic) schedules ### Changed - Disable automatic artwork database cleanup diff --git a/ErsatzTV.Core/Health/Checks/IEmptyScheduleHealthCheck.cs b/ErsatzTV.Core/Health/Checks/IEmptyScheduleHealthCheck.cs new file mode 100644 index 000000000..9dbb567ec --- /dev/null +++ b/ErsatzTV.Core/Health/Checks/IEmptyScheduleHealthCheck.cs @@ -0,0 +1,5 @@ +namespace ErsatzTV.Core.Health.Checks; + +public interface IEmptyScheduleHealthCheck : IHealthCheck +{ +} diff --git a/ErsatzTV.Infrastructure/Health/Checks/EmptyScheduleHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/EmptyScheduleHealthCheck.cs new file mode 100644 index 000000000..27cfee288 --- /dev/null +++ b/ErsatzTV.Infrastructure/Health/Checks/EmptyScheduleHealthCheck.cs @@ -0,0 +1,48 @@ +using ErsatzTV.Core.Health; +using ErsatzTV.Core.Health.Checks; +using ErsatzTV.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace ErsatzTV.Infrastructure.Health.Checks; + +public class EmptyScheduleHealthCheck(IDbContextFactory dbContextFactory) : BaseHealthCheck, IEmptyScheduleHealthCheck +{ + public override string Title => "Empty Schedule"; + + public async Task Check(CancellationToken cancellationToken) + { + await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); + + List scheduleIdsWithPlayouts = await dbContext.ProgramSchedules + .TagWithCallSite() + .AsNoTracking() + .Where(s => s.Playouts.Count > 0 && s.Items.Count == 0) + .Select(ps => ps.Id) + .ToListAsync(cancellationToken); + + List alternateScheduleIds = await dbContext.ProgramScheduleAlternates + .TagWithCallSite() + .AsNoTracking() + .Where(s => s.ProgramSchedule.Items.Count == 0) + .Select(s => s.ProgramScheduleId) + .ToListAsync(cancellationToken); + + var ids = scheduleIdsWithPlayouts.Union(alternateScheduleIds).ToHashSet(); + + if (ids.Count > 0) + { + List names = await dbContext.ProgramSchedules + .TagWithCallSite() + .AsNoTracking() + .Where(s => ids.Contains(s.Id)) + .Select(s => s.Name) + .ToListAsync(cancellationToken); + + return WarningResult( + $"There are {names.Count} empty schedules in use, which are NOT supported and WILL cause errors, including: {string.Join(", ", names.OrderBy(identity))}", + $"There are {names.Count} empty schedules in use, which are NOT supported and WILL cause errors"); + } + + return OkResult(); + } +} diff --git a/ErsatzTV.Infrastructure/Health/HealthCheckService.cs b/ErsatzTV.Infrastructure/Health/HealthCheckService.cs index cb2b41544..3ec586f7d 100644 --- a/ErsatzTV.Infrastructure/Health/HealthCheckService.cs +++ b/ErsatzTV.Infrastructure/Health/HealthCheckService.cs @@ -29,6 +29,7 @@ public class HealthCheckService : IHealthCheckService IErrorReportsHealthCheck errorReportsHealthCheck, IUnifiedDockerHealthCheck unifiedDockerHealthCheck, IDowngradeHealthCheck downgradeHealthCheck, + IEmptyScheduleHealthCheck emptyScheduleHealthCheck, IMemoryCache memoryCache, IMediator mediator, ILogger logger) @@ -49,6 +50,7 @@ public class HealthCheckService : IHealthCheckService zeroDurationHealthCheck, fileNotFoundHealthCheck, unavailableHealthCheck, + emptyScheduleHealthCheck, vaapiDriverHealthCheck, errorReportsHealthCheck ]; diff --git a/ErsatzTV/Startup.cs b/ErsatzTV/Startup.cs index 3ad735a00..3119207c6 100644 --- a/ErsatzTV/Startup.cs +++ b/ErsatzTV/Startup.cs @@ -772,6 +772,7 @@ public class Startup services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped();