Browse Source

fix mysql migration (#1561)

* clean up block playout preview logic

* fix some bugs with playout templates editor

* fix mysql migration
pull/1562/head
Jason Dove 2 years ago committed by GitHub
parent
commit
ef3b941a39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      ErsatzTV.Application/Scheduling/Commands/PreviewBlockPlayoutHandler.cs
  2. 9
      ErsatzTV.Core/Interfaces/Scheduling/IBlockPlayoutBuilder.cs
  3. 3
      ErsatzTV.Core/Interfaces/Scheduling/IBlockPlayoutPreviewBuilder.cs
  4. 132
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs
  5. 46
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutPreviewBuilder.cs
  6. 2
      ErsatzTV.Infrastructure.MySql/Migrations/20240114104626_Fix_BlockMinutes.cs
  7. 11
      ErsatzTV/Pages/PlayoutTemplatesEditor.razor
  8. 1
      ErsatzTV/Startup.cs

13
ErsatzTV.Application/Scheduling/Commands/PreviewBlockPlayoutHandler.cs

@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling; using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
@ -5,13 +6,13 @@ using ErsatzTV.Core.Scheduling;
using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions; using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
namespace ErsatzTV.Application.Scheduling; namespace ErsatzTV.Application.Scheduling;
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor")]
public class PreviewBlockPlayoutHandler( public class PreviewBlockPlayoutHandler(
IDbContextFactory<TvContext> dbContextFactory, IDbContextFactory<TvContext> dbContextFactory,
IBlockPlayoutBuilder blockPlayoutBuilder) IBlockPlayoutPreviewBuilder blockPlayoutBuilder)
: IRequestHandler<PreviewBlockPlayout, List<PlayoutItemPreviewViewModel>> : IRequestHandler<PreviewBlockPlayout, List<PlayoutItemPreviewViewModel>>
{ {
public async Task<List<PlayoutItemPreviewViewModel>> Handle( public async Task<List<PlayoutItemPreviewViewModel>> Handle(
@ -55,13 +56,7 @@ public class PreviewBlockPlayoutHandler(
] ]
}; };
await blockPlayoutBuilder.Build( await blockPlayoutBuilder.Build(playout, PlayoutBuildMode.Reset, cancellationToken);
playout,
PlayoutBuildMode.Reset,
NullLogger.Instance,
1,
randomizeStartPoints: true,
cancellationToken);
// load playout item details for title // load playout item details for title
foreach (PlayoutItem playoutItem in playout.Items) foreach (PlayoutItem playoutItem in playout.Items)

9
ErsatzTV.Core/Interfaces/Scheduling/IBlockPlayoutBuilder.cs

@ -1,18 +1,9 @@
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Scheduling; using ErsatzTV.Core.Scheduling;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.Core.Interfaces.Scheduling; namespace ErsatzTV.Core.Interfaces.Scheduling;
public interface IBlockPlayoutBuilder public interface IBlockPlayoutBuilder
{ {
Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken); Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken);
Task<Playout> Build(
Playout playout,
PlayoutBuildMode mode,
ILogger customLogger,
int daysToBuild,
bool randomizeStartPoints,
CancellationToken cancellationToken);
} }

3
ErsatzTV.Core/Interfaces/Scheduling/IBlockPlayoutPreviewBuilder.cs

@ -0,0 +1,3 @@
namespace ErsatzTV.Core.Interfaces.Scheduling;
public interface IBlockPlayoutPreviewBuilder : IBlockPlayoutBuilder;

132
ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs

@ -19,24 +19,7 @@ public class BlockPlayoutBuilder(
{ {
public async Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken) public async Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken)
{ {
int daysToBuild = await configElementRepository.GetValue<int>(ConfigElementKey.PlayoutDaysToBuild) Logger.LogDebug(
.IfNoneAsync(2);
return await Build(playout, mode, logger, daysToBuild, randomizeStartPoints: false, cancellationToken);
}
public async Task<Playout> Build(
Playout playout,
PlayoutBuildMode mode,
ILogger customLogger,
int daysToBuild,
bool randomizeStartPoints,
CancellationToken cancellationToken)
{
// ReSharper disable once LocalVariableHidesPrimaryConstructorParameter
ILogger log = customLogger ?? logger;
log.LogDebug(
"Building block playout {PlayoutId} for channel {ChannelNumber} - {ChannelName}", "Building block playout {PlayoutId} for channel {ChannelNumber} - {ChannelName}",
playout.Id, playout.Id,
playout.Channel.Number, playout.Channel.Number,
@ -51,6 +34,7 @@ public class BlockPlayoutBuilder(
DateTimeOffset start = DateTimeOffset.Now; DateTimeOffset start = DateTimeOffset.Now;
int daysToBuild = await GetDaysToBuild();
// get blocks to schedule // get blocks to schedule
List<EffectiveBlock> blocksToSchedule = EffectiveBlock.GetEffectiveBlocks(playout, start, daysToBuild); List<EffectiveBlock> blocksToSchedule = EffectiveBlock.GetEffectiveBlocks(playout, start, daysToBuild);
@ -80,14 +64,14 @@ public class BlockPlayoutBuilder(
{ {
currentTime = effectiveBlock.Start; currentTime = effectiveBlock.Start;
log.LogDebug( Logger.LogDebug(
"Will schedule block {Block} at {Start}", "Will schedule block {Block} at {Start}",
effectiveBlock.Block.Name, effectiveBlock.Block.Name,
effectiveBlock.Start); effectiveBlock.Start);
} }
else else
{ {
log.LogDebug( Logger.LogDebug(
"Will schedule block {Block} with start {Start} at {ActualStart}", "Will schedule block {Block} with start {Start} at {ActualStart}",
effectiveBlock.Block.Name, effectiveBlock.Block.Name,
effectiveBlock.Start, effectiveBlock.Start,
@ -106,7 +90,7 @@ public class BlockPlayoutBuilder(
if (currentTime >= blockFinish) if (currentTime >= blockFinish)
{ {
log.LogDebug( Logger.LogDebug(
"Current time {Time} for block {Block} is beyond block finish {Finish}; will stop with this block's items", "Current time {Time} for block {Block} is beyond block finish {Finish}; will stop with this block's items",
currentTime, currentTime,
effectiveBlock.Block.Name, effectiveBlock.Block.Name,
@ -119,54 +103,16 @@ public class BlockPlayoutBuilder(
string historyKey = HistoryDetails.KeyForBlockItem(blockItem); string historyKey = HistoryDetails.KeyForBlockItem(blockItem);
//logger.LogDebug("History key for block item {Item} is {Key}", blockItem.Id, historyKey); //logger.LogDebug("History key for block item {Item} is {Key}", blockItem.Id, historyKey);
DateTime historyTime = currentTime.UtcDateTime; IMediaCollectionEnumerator enumerator = GetEnumerator(
Option<PlayoutHistory> maybeHistory = playout.PlayoutHistory playout,
.Filter(h => h.BlockId == blockItem.BlockId) blockItem,
.Filter(h => h.Key == historyKey) currentTime,
.Filter(h => h.When < historyTime) historyKey,
.OrderByDescending(h => h.When) collectionMediaItems);
.HeadOrNone();
var state = new CollectionEnumeratorState { Seed = 0, Index = 0 };
var collectionKey = CollectionKey.ForBlockItem(blockItem);
List<MediaItem> collectionItems = collectionMediaItems[collectionKey];
// get enumerator
IMediaCollectionEnumerator enumerator = blockItem.PlaybackOrder switch
{
PlaybackOrder.Chronological => new ChronologicalMediaCollectionEnumerator(collectionItems, state),
PlaybackOrder.SeasonEpisode => new SeasonEpisodeMediaCollectionEnumerator(collectionItems, state),
_ => new RandomizedMediaCollectionEnumerator(
collectionItems,
new CollectionEnumeratorState { Seed = new Random().Next(), Index = 0 })
};
// seek to the appropriate place in the collection enumerator
foreach (PlayoutHistory history in maybeHistory)
{
log.LogDebug("History is applicable: {When}: {History}", history.When, history.Details);
HistoryDetails.MoveToNextItem(
collectionItems,
history.Details,
enumerator,
blockItem.PlaybackOrder);
}
if (maybeHistory.IsNone && randomizeStartPoints)
{
enumerator.ResetState(
new CollectionEnumeratorState
{
Seed = new Random().Next(),
Index = new Random().Next(collectionItems.Count)
});
}
foreach (MediaItem mediaItem in enumerator.Current) foreach (MediaItem mediaItem in enumerator.Current)
{ {
log.LogDebug( Logger.LogDebug(
"current item: {Id} / {Title}", "current item: {Id} / {Title}",
mediaItem.Id, mediaItem.Id,
mediaItem is Episode e ? GetTitle(e) : string.Empty); mediaItem is Episode e ? GetTitle(e) : string.Empty);
@ -217,6 +163,60 @@ public class BlockPlayoutBuilder(
return playout; return playout;
} }
protected virtual ILogger Logger => logger;
protected virtual async Task<int> GetDaysToBuild()
{
return await configElementRepository
.GetValue<int>(ConfigElementKey.PlayoutDaysToBuild)
.IfNoneAsync(2);
}
protected virtual IMediaCollectionEnumerator GetEnumerator(
Playout playout,
BlockItem blockItem,
DateTimeOffset currentTime,
string historyKey,
Map<CollectionKey, List<MediaItem>> collectionMediaItems)
{
DateTime historyTime = currentTime.UtcDateTime;
Option<PlayoutHistory> maybeHistory = playout.PlayoutHistory
.Filter(h => h.BlockId == blockItem.BlockId)
.Filter(h => h.Key == historyKey)
.Filter(h => h.When < historyTime)
.OrderByDescending(h => h.When)
.HeadOrNone();
var state = new CollectionEnumeratorState { Seed = 0, Index = 0 };
var collectionKey = CollectionKey.ForBlockItem(blockItem);
List<MediaItem> collectionItems = collectionMediaItems[collectionKey];
// get enumerator
IMediaCollectionEnumerator enumerator = blockItem.PlaybackOrder switch
{
PlaybackOrder.Chronological => new ChronologicalMediaCollectionEnumerator(collectionItems, state),
PlaybackOrder.SeasonEpisode => new SeasonEpisodeMediaCollectionEnumerator(collectionItems, state),
_ => new RandomizedMediaCollectionEnumerator(
collectionItems,
new CollectionEnumeratorState { Seed = new Random().Next(), Index = 0 })
};
// seek to the appropriate place in the collection enumerator
foreach (PlayoutHistory history in maybeHistory)
{
Logger.LogDebug("History is applicable: {When}: {History}", history.When, history.Details);
HistoryDetails.MoveToNextItem(
collectionItems,
history.Details,
enumerator,
blockItem.PlaybackOrder);
}
return enumerator;
}
private static string GetTitle(Episode e) private static string GetTitle(Episode e)
{ {
string showTitle = e.Season.Show.ShowMetadata.HeadOrNone() string showTitle = e.Season.Show.ShowMetadata.HeadOrNone()

46
ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutPreviewBuilder.cs

@ -0,0 +1,46 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Scheduling;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace ErsatzTV.Core.Scheduling.BlockScheduling;
public class BlockPlayoutPreviewBuilder(
IConfigElementRepository configElementRepository,
IMediaCollectionRepository mediaCollectionRepository,
ITelevisionRepository televisionRepository,
IArtistRepository artistRepository,
ILogger<BlockPlayoutBuilder> logger) : BlockPlayoutBuilder(
configElementRepository,
mediaCollectionRepository,
televisionRepository,
artistRepository,
logger), IBlockPlayoutPreviewBuilder
{
protected override ILogger Logger => NullLogger.Instance;
protected override Task<int> GetDaysToBuild() => Task.FromResult(1);
protected override IMediaCollectionEnumerator GetEnumerator(
Playout playout,
BlockItem blockItem,
DateTimeOffset currentTime,
string historyKey,
Map<CollectionKey, List<MediaItem>> collectionMediaItems)
{
IMediaCollectionEnumerator enumerator = base.GetEnumerator(playout, blockItem, currentTime, historyKey, collectionMediaItems);
var collectionKey = CollectionKey.ForBlockItem(blockItem);
enumerator.ResetState(
new CollectionEnumeratorState
{
Seed = new Random().Next(),
Index = new Random().Next(collectionMediaItems[collectionKey].Count)
});
return enumerator;
}
}

2
ErsatzTV.Infrastructure.MySql/Migrations/20240114104626_Fix_BlockMinutes.cs

@ -10,7 +10,7 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
{ {
migrationBuilder.Sql(@"UPDATE Block SET Minutes = CAST(CEILING(Minutes / 15.0) * 15 AS INT)"); migrationBuilder.Sql(@"UPDATE Block SET Minutes = CAST(CEILING(Minutes / 15.0) * 15 AS UNSIGNED)");
} }
/// <inheritdoc /> /// <inheritdoc />

11
ErsatzTV/Pages/PlayoutTemplatesEditor.razor

@ -70,8 +70,7 @@
</MudTd> </MudTd>
<MudTd> <MudTd>
<MudIconButton Icon="@Icons.Material.Filled.Delete" <MudIconButton Icon="@Icons.Material.Filled.Delete"
OnClick="@(_ => DeleteTemplate(context))" OnClick="@(_ => DeleteTemplate(context))">
Disabled="@(_items.Count == 1)">
</MudIconButton> </MudIconButton>
</MudTd> </MudTd>
</RowTemplate> </RowTemplate>
@ -424,6 +423,12 @@
private async Task SaveChanges() private async Task SaveChanges()
{ {
if (_items.Any(i => i.Template is null))
{
Snackbar.Add("Unable to save; item has no template selected", Severity.Error);
return;
}
var items = _items.Map(item => new ReplacePlayoutTemplate( var items = _items.Map(item => new ReplacePlayoutTemplate(
item.Id, item.Id,
item.Index, item.Index,
@ -455,7 +460,7 @@
_previewItems.Clear(); _previewItems.Clear();
var prioritized = _items.OrderBy(t => t.Index).ToList(); var prioritized = _items.Filter(i => i.Template is not null).OrderBy(t => t.Index).ToList();
if (dateRange.Start.HasValue && dateRange.End.HasValue) if (dateRange.Start.HasValue && dateRange.End.HasValue)
{ {

1
ErsatzTV/Startup.cs

@ -653,6 +653,7 @@ public class Startup
services.AddScoped<IExternalJsonPlayoutItemProvider, ExternalJsonPlayoutItemProvider>(); services.AddScoped<IExternalJsonPlayoutItemProvider, ExternalJsonPlayoutItemProvider>();
services.AddScoped<IPlayoutBuilder, PlayoutBuilder>(); services.AddScoped<IPlayoutBuilder, PlayoutBuilder>();
services.AddScoped<IBlockPlayoutBuilder, BlockPlayoutBuilder>(); services.AddScoped<IBlockPlayoutBuilder, BlockPlayoutBuilder>();
services.AddScoped<IBlockPlayoutPreviewBuilder, BlockPlayoutPreviewBuilder>();
services.AddScoped<IExternalJsonPlayoutBuilder, ExternalJsonPlayoutBuilder>(); services.AddScoped<IExternalJsonPlayoutBuilder, ExternalJsonPlayoutBuilder>();
services.AddScoped<IImageCache, ImageCache>(); services.AddScoped<IImageCache, ImageCache>();
services.AddScoped<ILocalFileSystem, LocalFileSystem>(); services.AddScoped<ILocalFileSystem, LocalFileSystem>();

Loading…
Cancel
Save