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;
|
||||||
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)
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
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()
|
||||||
|
|||||||
@@ -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 />
|
/// <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 />
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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>();
|
||||||
|
|||||||
Reference in New Issue
Block a user