using System.Threading.Channels; using ErsatzTV.Application.Playouts; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Scheduling; using ErsatzTV.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using static ErsatzTV.Application.ProgramSchedules.Mapper; namespace ErsatzTV.Application.ProgramSchedules; public class ReplaceProgramScheduleItemsHandler : ProgramScheduleItemCommandBase, IRequestHandler>> { private readonly ChannelWriter _channel; private readonly IDbContextFactory _dbContextFactory; public ReplaceProgramScheduleItemsHandler( IDbContextFactory dbContextFactory, ChannelWriter channel) { _dbContextFactory = dbContextFactory; _channel = channel; } public async Task>> Handle( ReplaceProgramScheduleItems request, CancellationToken cancellationToken) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); Validation validation = await Validate(dbContext, request); return await LanguageExtensions.Apply(validation, ps => PersistItems(dbContext, request, ps)); } private async Task> PersistItems( TvContext dbContext, ReplaceProgramScheduleItems request, ProgramSchedule programSchedule) { dbContext.RemoveRange(programSchedule.Items); programSchedule.Items = request.Items.Map(i => BuildItem(programSchedule, i.Index, i)).ToList(); await dbContext.SaveChangesAsync(); // refresh any playouts that use this schedule foreach (Playout playout in programSchedule.Playouts) { await _channel.WriteAsync(new BuildPlayout(playout.Id, PlayoutBuildMode.Refresh)); } return programSchedule.Items.Map(ProjectToViewModel); } private Task> Validate( TvContext dbContext, ReplaceProgramScheduleItems request) => ProgramScheduleMustExist(dbContext, request.ProgramScheduleId) .BindT(programSchedule => PlayoutModesMustBeValid(request, programSchedule)) .BindT(programSchedule => CollectionTypesMustBeValid(request, programSchedule)) .BindT(programSchedule => PlaybackOrdersMustBeValid(request, programSchedule)) .BindT(programSchedule => FillerConfigurationsMustBeValid(dbContext, request, programSchedule)); private static Validation PlayoutModesMustBeValid( ReplaceProgramScheduleItems request, ProgramSchedule programSchedule) => request.Items.Map(item => PlayoutModeMustBeValid(item, programSchedule)).Sequence() .Map(_ => programSchedule); private Validation CollectionTypesMustBeValid( ReplaceProgramScheduleItems request, ProgramSchedule programSchedule) => request.Items.Map(item => CollectionTypeMustBeValid(item, programSchedule)).Sequence() .Map(_ => programSchedule); private static async Task> FillerConfigurationsMustBeValid( TvContext dbContext, ReplaceProgramScheduleItems request, ProgramSchedule programSchedule) { foreach (ReplaceProgramScheduleItem item in request.Items) { Either result = await FillerConfigurationMustBeValid( dbContext, item, programSchedule); if (result.IsLeft) { return result.ToValidation(); } } return programSchedule; } private static Validation PlaybackOrdersMustBeValid( ReplaceProgramScheduleItems request, ProgramSchedule programSchedule) { var keyOrders = new Dictionary>(); foreach (ReplaceProgramScheduleItem item in request.Items) { var key = new CollectionKey( item.CollectionType, item.CollectionId, item.MediaItemId, item.MultiCollectionId, item.SmartCollectionId); if (keyOrders.TryGetValue(key, out System.Collections.Generic.HashSet playbackOrders)) { playbackOrders.Add(item.PlaybackOrder); keyOrders[key] = playbackOrders; } else { keyOrders.Add(key, new System.Collections.Generic.HashSet { item.PlaybackOrder }); } } return Optional(keyOrders.Values.Count(set => set.Count != 1)) .Filter(count => count == 0) .Map(_ => programSchedule) .ToValidation("A collection must not use multiple playback orders"); } private record CollectionKey( ProgramScheduleItemCollectionType CollectionType, int? CollectionId, int? MediaItemId, int? MultiCollectionId, int? SmartCollectionId); }