Browse Source

channel mirror validation improvements (#2396)

* improve channel mirror validation

* fix playout offset
pull/2397/head
Jason Dove 4 months ago committed by GitHub
parent
commit
d8e8abb691
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 48
      ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs
  2. 61
      ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs
  3. 10
      ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs
  4. 2
      ErsatzTV.Core/Scheduling/PlayoutReferenceData.cs
  5. 9
      ErsatzTV/Pages/ChannelEditor.razor

48
ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs

@ -39,13 +39,15 @@ public class CreateChannelHandler( @@ -39,13 +39,15 @@ public class CreateChannelHandler(
(ValidateName(request), await ValidateNumber(dbContext, request, cancellationToken),
await FFmpegProfileMustExist(dbContext, request, cancellationToken),
await WatermarkMustExist(dbContext, request, cancellationToken),
await FillerPresetMustExist(dbContext, request, cancellationToken))
await FillerPresetMustExist(dbContext, request, cancellationToken),
await MirrorSourceMustBeValid(dbContext, request, cancellationToken))
.Apply((
name,
number,
ffmpegProfileId,
watermarkId,
fillerPresetId) =>
fillerPresetId,
_) =>
{
var artwork = new List<Artwork>();
if (!string.IsNullOrWhiteSpace(request.Logo?.Path))
@ -192,4 +194,46 @@ public class CreateChannelHandler( @@ -192,4 +194,46 @@ public class CreateChannelHandler(
.Map(o => o.ToValidation<BaseError>(
$"Fallback filler {createChannel.FallbackFillerId} does not exist."));
}
private static async Task<Validation<BaseError, Unit>> MirrorSourceMustBeValid(
TvContext dbContext,
CreateChannel createChannel,
CancellationToken cancellationToken)
{
if (createChannel.PlayoutSource is not ChannelPlayoutSource.Mirror)
{
return Unit.Default;
}
Option<Channel> maybeMirrorSource = await dbContext.Channels
.AsNoTracking()
.SelectOneAsync(
c => c.Id == createChannel.MirrorSourceChannelId,
c => c.Id == createChannel.MirrorSourceChannelId,
cancellationToken);
if (maybeMirrorSource.IsNone)
{
return BaseError.New("Mirror source channel does not exist.");
}
foreach (var mirrorSource in maybeMirrorSource)
{
if (mirrorSource.PlayoutSource is not ChannelPlayoutSource.Generated)
{
return BaseError.New(
$"Mirror source channel {mirrorSource.Name} must use generated playout source");
}
}
foreach (TimeSpan playoutOffset in Optional(createChannel.PlayoutOffset))
{
if (playoutOffset < TimeSpan.FromHours(-12) || playoutOffset > TimeSpan.FromHours(12))
{
return BaseError.New("Playout offset must not be greater than 12 hours");
}
}
return Unit.Default;
}
}

61
ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs

@ -33,6 +33,16 @@ public class UpdateChannelHandler( @@ -33,6 +33,16 @@ public class UpdateChannelHandler(
UpdateChannel update,
CancellationToken cancellationToken)
{
// don't save mirror when playout exists
if (c.Playouts.Count > 0)
{
update = update with
{
PlayoutSource = ChannelPlayoutSource.Generated,
MirrorSourceChannelId = null
};
}
bool hasEpgChange = c.PlayoutSource != update.PlayoutSource || c.ShowInEpg != update.ShowInEpg;
c.Name = update.Name;
@ -150,9 +160,11 @@ public class UpdateChannelHandler( @@ -150,9 +160,11 @@ public class UpdateChannelHandler(
TvContext dbContext,
UpdateChannel request,
CancellationToken cancellationToken) =>
(await ChannelMustExist(dbContext, request, cancellationToken), ValidateName(request),
await ValidateNumber(dbContext, request, cancellationToken))
.Apply((channelToUpdate, _, _) => channelToUpdate);
(await ChannelMustExist(dbContext, request, cancellationToken),
ValidateName(request),
await ValidateNumber(dbContext, request, cancellationToken),
await MirrorSourceMustBeValid(dbContext, request, cancellationToken))
.Apply((channelToUpdate, _, _, _) => channelToUpdate);
private static Task<Validation<BaseError, Channel>> ChannelMustExist(
TvContext dbContext,
@ -161,9 +173,52 @@ public class UpdateChannelHandler( @@ -161,9 +173,52 @@ public class UpdateChannelHandler(
dbContext.Channels
.Include(c => c.Artwork)
.Include(c => c.Watermark)
.Include(c => c.Playouts)
.SelectOneAsync(c => c.Id, c => c.Id == updateChannel.ChannelId, cancellationToken)
.Map(o => o.ToValidation<BaseError>("Channel does not exist."));
private static async Task<Validation<BaseError, Unit>> MirrorSourceMustBeValid(
TvContext dbContext,
UpdateChannel request,
CancellationToken cancellationToken)
{
if (request.PlayoutSource is not ChannelPlayoutSource.Mirror)
{
return Unit.Default;
}
Option<Channel> maybeMirrorSource = await dbContext.Channels
.AsNoTracking()
.SelectOneAsync(
c => c.Id == request.MirrorSourceChannelId,
c => c.Id == request.MirrorSourceChannelId,
cancellationToken);
if (maybeMirrorSource.IsNone)
{
return BaseError.New("Mirror source channel does not exist.");
}
foreach (var mirrorSource in maybeMirrorSource)
{
if (mirrorSource.PlayoutSource is not ChannelPlayoutSource.Generated)
{
return BaseError.New(
$"Mirror source channel {mirrorSource.Name} must use generated playout source");
}
}
foreach (TimeSpan playoutOffset in Optional(request.PlayoutOffset))
{
if (playoutOffset < TimeSpan.FromHours(-12) || playoutOffset > TimeSpan.FromHours(12))
{
return BaseError.New("Playout offset must not be greater than 12 hours");
}
}
return Unit.Default;
}
private static Validation<BaseError, string> ValidateName(UpdateChannel updateChannel) =>
updateChannel.NotEmpty(c => c.Name)
.Bind(_ => updateChannel.NotLongerThan(50)(c => c.Name));

10
ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs

@ -171,7 +171,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -171,7 +171,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
{
changeCount += await dbContext.PlayoutItems
.Where(pi => pi.PlayoutId == playout.Id)
.Where(pi => pi.Finish < removeBefore.UtcDateTime + referenceData.MinPlayoutOffset)
.Where(pi => pi.Finish < removeBefore.UtcDateTime - referenceData.MaxPlayoutOffset)
.ExecuteDeleteAsync(cancellationToken);
}
@ -349,7 +349,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -349,7 +349,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
.Where(c => c.Playouts.Any(p => p.Id == playoutId))
.FirstOrDefaultAsync();
TimeSpan minPlayoutOffset = TimeSpan.Zero;
TimeSpan maxPlayoutOffset = TimeSpan.Zero;
List<Channel> mirrorChannels = await dbContext.Channels
.AsNoTracking()
.Where(c => c.MirrorSourceChannelId == channel.Id)
@ -357,9 +357,9 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -357,9 +357,9 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
foreach (var mirrorChannel in mirrorChannels)
{
var offset = mirrorChannel.PlayoutOffset ?? TimeSpan.Zero;
if (offset < minPlayoutOffset)
if (offset > maxPlayoutOffset)
{
minPlayoutOffset = offset;
maxPlayoutOffset = offset;
}
}
@ -465,6 +465,6 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -465,6 +465,6 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
programSchedule,
programScheduleAlternates,
playoutHistory,
minPlayoutOffset);
maxPlayoutOffset);
}
}

2
ErsatzTV.Core/Scheduling/PlayoutReferenceData.cs

@ -11,4 +11,4 @@ public record PlayoutReferenceData( @@ -11,4 +11,4 @@ public record PlayoutReferenceData(
ProgramSchedule ProgramSchedule,
List<ProgramScheduleAlternate> ProgramScheduleAlternates,
List<PlayoutHistory> PlayoutHistory,
TimeSpan MinPlayoutOffset);
TimeSpan MaxPlayoutOffset);

9
ErsatzTV/Pages/ChannelEditor.razor

@ -72,7 +72,10 @@ @@ -72,7 +72,10 @@
<div class="d-flex">
<MudText>Playout Source</MudText>
</div>
<MudSelect @bind-Value="_model.PlayoutSource" For="@(() => _model.PlayoutSource)" HelperText="Controls the source of the channel's content">
<MudSelect @bind-Value="_model.PlayoutSource"
For="@(() => _model.PlayoutSource)"
HelperText="Controls the source of the channel's content. This cannot be changed when the channel already has a playout."
Disabled="@(_channels.Any(c => c.Id == Id && c.PlayoutCount > 0))">
<MudSelectItem Value="@(ChannelPlayoutSource.Generated)">Generated</MudSelectItem>
<MudSelectItem Value="@(ChannelPlayoutSource.Mirror)">Mirror</MudSelectItem>
</MudSelect>
@ -86,7 +89,9 @@ @@ -86,7 +89,9 @@
<MudSelect @bind-Value="_model.MirrorSourceChannelId" For="@(() => _model.MirrorSourceChannelId)"
Clearable="true">
<MudSelectItem T="int?" Value="@((int?)null)">(none)</MudSelectItem>
@foreach (ChannelViewModel channel in _channels.OrderBy(c => decimal.Parse(c.Number, CultureInfo.InvariantCulture)))
@foreach (ChannelViewModel channel in _channels
.Where(c => c.Id != Id && c.PlayoutSource is ChannelPlayoutSource.Generated)
.OrderBy(c => decimal.Parse(c.Number, CultureInfo.InvariantCulture)))
{
<MudSelectItem T="int?" Value="@channel.Id">(@channel.Number) - @channel.Name</MudSelectItem>
}

Loading…
Cancel
Save