using System.Threading.Channels; using ErsatzTV.Application.Playouts; using ErsatzTV.Core; using ErsatzTV.Core.Scheduling; using ErsatzTV.Infrastructure.Data; using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Application.Scheduling; public class UpdateDefaultDecoHandler( IDbContextFactory dbContextFactory, ChannelWriter channel) : IRequestHandler> { public async Task> Handle(UpdateDefaultDeco request, CancellationToken cancellationToken) { try { await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); int updated = await dbContext.Playouts .Where(p => p.Id == request.PlayoutId) .ExecuteUpdateAsync( u => u .SetProperty(p => p.DecoId, p => request.DecoId) // Bump the shared Playout.Version so any open alternate-schedule/template editor tab // (which round-trips this same token) detects the deco change and reloads (#253 §7a). // Bulk ExecuteUpdate bypasses change-tracking, so bump via SetProperty, not Version++. .SetProperty(p => p.Version, p => p.Version + 1), cancellationToken); // Changing a playout's default deco only takes effect on a Reset build (deco content is applied // during Reset, and BlockKey has no deco dimension to self-heal) — same stale-until-Reset class // as #251. Enqueue a Reset for the affected playout, but only if a row was actually updated: a // request for a nonexistent playout must not queue a build for an id that isn't there (keeps the // enqueued set equal to the affected set). CancellationToken.None so the rebuild isn't dropped // after the assignment has already been persisted. if (updated > 0) { await channel.WriteAsync( new BuildPlayout(request.PlayoutId, PlayoutBuildMode.Reset), CancellationToken.None); } return Option.None; } catch (Exception ex) { return BaseError.New(ex.ToString()); } } }