fix mysql migration (#1561)
* clean up block playout preview logic * fix some bugs with playout templates editor * fix mysql migration
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Domain.Scheduling;
|
||||
using ErsatzTV.Core.Interfaces.Scheduling;
|
||||
@@ -5,13 +6,13 @@ using ErsatzTV.Core.Scheduling;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
using ErsatzTV.Infrastructure.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace ErsatzTV.Application.Scheduling;
|
||||
|
||||
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor")]
|
||||
public class PreviewBlockPlayoutHandler(
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
IBlockPlayoutBuilder blockPlayoutBuilder)
|
||||
IBlockPlayoutPreviewBuilder blockPlayoutBuilder)
|
||||
: IRequestHandler<PreviewBlockPlayout, List<PlayoutItemPreviewViewModel>>
|
||||
{
|
||||
public async Task<List<PlayoutItemPreviewViewModel>> Handle(
|
||||
@@ -55,13 +56,7 @@ public class PreviewBlockPlayoutHandler(
|
||||
]
|
||||
};
|
||||
|
||||
await blockPlayoutBuilder.Build(
|
||||
playout,
|
||||
PlayoutBuildMode.Reset,
|
||||
NullLogger.Instance,
|
||||
1,
|
||||
randomizeStartPoints: true,
|
||||
cancellationToken);
|
||||
await blockPlayoutBuilder.Build(playout, PlayoutBuildMode.Reset, cancellationToken);
|
||||
|
||||
// load playout item details for title
|
||||
foreach (PlayoutItem playoutItem in playout.Items)
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Scheduling;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ErsatzTV.Core.Interfaces.Scheduling;
|
||||
|
||||
public interface IBlockPlayoutBuilder
|
||||
{
|
||||
Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken);
|
||||
|
||||
Task<Playout> Build(
|
||||
Playout playout,
|
||||
PlayoutBuildMode mode,
|
||||
ILogger customLogger,
|
||||
int daysToBuild,
|
||||
bool randomizeStartPoints,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Core.Interfaces.Scheduling;
|
||||
|
||||
public interface IBlockPlayoutPreviewBuilder : IBlockPlayoutBuilder;
|
||||
@@ -19,24 +19,7 @@ public class BlockPlayoutBuilder(
|
||||
{
|
||||
public async Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken)
|
||||
{
|
||||
int daysToBuild = await configElementRepository.GetValue<int>(ConfigElementKey.PlayoutDaysToBuild)
|
||||
.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(
|
||||
Logger.LogDebug(
|
||||
"Building block playout {PlayoutId} for channel {ChannelNumber} - {ChannelName}",
|
||||
playout.Id,
|
||||
playout.Channel.Number,
|
||||
@@ -51,6 +34,7 @@ public class BlockPlayoutBuilder(
|
||||
|
||||
DateTimeOffset start = DateTimeOffset.Now;
|
||||
|
||||
int daysToBuild = await GetDaysToBuild();
|
||||
|
||||
// get blocks to schedule
|
||||
List<EffectiveBlock> blocksToSchedule = EffectiveBlock.GetEffectiveBlocks(playout, start, daysToBuild);
|
||||
@@ -80,14 +64,14 @@ public class BlockPlayoutBuilder(
|
||||
{
|
||||
currentTime = effectiveBlock.Start;
|
||||
|
||||
log.LogDebug(
|
||||
Logger.LogDebug(
|
||||
"Will schedule block {Block} at {Start}",
|
||||
effectiveBlock.Block.Name,
|
||||
effectiveBlock.Start);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.LogDebug(
|
||||
Logger.LogDebug(
|
||||
"Will schedule block {Block} with start {Start} at {ActualStart}",
|
||||
effectiveBlock.Block.Name,
|
||||
effectiveBlock.Start,
|
||||
@@ -106,7 +90,7 @@ public class BlockPlayoutBuilder(
|
||||
|
||||
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",
|
||||
currentTime,
|
||||
effectiveBlock.Block.Name,
|
||||
@@ -119,54 +103,16 @@ public class BlockPlayoutBuilder(
|
||||
string historyKey = HistoryDetails.KeyForBlockItem(blockItem);
|
||||
//logger.LogDebug("History key for block item {Item} is {Key}", blockItem.Id, historyKey);
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
});
|
||||
}
|
||||
IMediaCollectionEnumerator enumerator = GetEnumerator(
|
||||
playout,
|
||||
blockItem,
|
||||
currentTime,
|
||||
historyKey,
|
||||
collectionMediaItems);
|
||||
|
||||
foreach (MediaItem mediaItem in enumerator.Current)
|
||||
{
|
||||
log.LogDebug(
|
||||
Logger.LogDebug(
|
||||
"current item: {Id} / {Title}",
|
||||
mediaItem.Id,
|
||||
mediaItem is Episode e ? GetTitle(e) : string.Empty);
|
||||
@@ -217,6 +163,60 @@ public class BlockPlayoutBuilder(
|
||||
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)
|
||||
{
|
||||
string showTitle = e.Season.Show.ShowMetadata.HeadOrNone()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
/// <inheritdoc />
|
||||
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 />
|
||||
|
||||
@@ -70,8 +70,7 @@
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
OnClick="@(_ => DeleteTemplate(context))"
|
||||
Disabled="@(_items.Count == 1)">
|
||||
OnClick="@(_ => DeleteTemplate(context))">
|
||||
</MudIconButton>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
@@ -424,6 +423,12 @@
|
||||
|
||||
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(
|
||||
item.Id,
|
||||
item.Index,
|
||||
@@ -455,7 +460,7 @@
|
||||
|
||||
_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)
|
||||
{
|
||||
|
||||
@@ -653,6 +653,7 @@ public class Startup
|
||||
services.AddScoped<IExternalJsonPlayoutItemProvider, ExternalJsonPlayoutItemProvider>();
|
||||
services.AddScoped<IPlayoutBuilder, PlayoutBuilder>();
|
||||
services.AddScoped<IBlockPlayoutBuilder, BlockPlayoutBuilder>();
|
||||
services.AddScoped<IBlockPlayoutPreviewBuilder, BlockPlayoutPreviewBuilder>();
|
||||
services.AddScoped<IExternalJsonPlayoutBuilder, ExternalJsonPlayoutBuilder>();
|
||||
services.AddScoped<IImageCache, ImageCache>();
|
||||
services.AddScoped<ILocalFileSystem, LocalFileSystem>();
|
||||
|
||||
Reference in New Issue
Block a user