Browse Source

rename yaml playout to sequential schedule (#2335)

* clarify some schedule and playout terms

* more renaming
pull/2339/head
Jason Dove 4 months ago committed by GitHub
parent
commit
e06ee54070
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 8
      CHANGELOG.md
  2. 10
      ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs
  3. 2
      ErsatzTV.Application/MediaCollections/Commands/PreviewPlaylistPlayoutHandler.cs
  4. 24
      ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs
  5. 14
      ErsatzTV.Application/Playouts/Commands/CreateBlockPlayoutHandler.cs
  6. 32
      ErsatzTV.Application/Playouts/Commands/CreateClassicPlayoutHandler.cs
  7. 14
      ErsatzTV.Application/Playouts/Commands/CreateExternalJsonPlayoutHandler.cs
  8. 14
      ErsatzTV.Application/Playouts/Commands/CreatePlayout.cs
  9. 34
      ErsatzTV.Application/Playouts/Commands/CreateSequentialPlayoutHandler.cs
  10. 12
      ErsatzTV.Application/Playouts/Commands/ResetAllPlayoutsHandler.cs
  11. 2
      ErsatzTV.Application/Playouts/Commands/UpdateExternalJsonPlayoutHandler.cs
  12. 2
      ErsatzTV.Application/Playouts/Commands/UpdatePlayoutHandler.cs
  13. 2
      ErsatzTV.Application/Playouts/Commands/UpdateSequentialPlayout.cs
  14. 14
      ErsatzTV.Application/Playouts/Commands/UpdateSequentialPlayoutHandler.cs
  15. 2
      ErsatzTV.Application/Playouts/PlayoutNameViewModel.cs
  16. 2
      ErsatzTV.Application/Playouts/PlayoutViewModel.cs
  17. 2
      ErsatzTV.Application/Playouts/Queries/GetAllPlayoutsHandler.cs
  18. 2
      ErsatzTV.Application/Playouts/Queries/GetPlayoutByIdHandler.cs
  19. 4
      ErsatzTV.Application/Scheduling/Commands/ErasePlayoutHistoryHandler.cs
  20. 4
      ErsatzTV.Application/Scheduling/Commands/ErasePlayoutItemsHandler.cs
  21. 2
      ErsatzTV.Application/Scheduling/Commands/PreviewBlockPlayoutHandler.cs
  22. 12
      ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs
  23. 2
      ErsatzTV.Core/Domain/Playout.cs
  24. 4
      ErsatzTV.Core/Domain/PlayoutScheduleKind.cs
  25. 2
      ErsatzTV.Core/Interfaces/Scheduling/ISequentialScheduleValidator.cs
  26. 4
      ErsatzTV.Core/Scheduling/PlayoutBuilder.cs
  27. 2
      ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutRepeatHandler.cs
  28. 4
      ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs
  29. 6331
      ErsatzTV.Infrastructure.MySql/Migrations/20250823133839_Rename_ProgramSchedulePlayoutType_ScheduleKind.Designer.cs
  30. 28
      ErsatzTV.Infrastructure.MySql/Migrations/20250823133839_Rename_ProgramSchedulePlayoutType_ScheduleKind.cs
  31. 2
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  32. 6166
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250823133952_Rename_ProgramSchedulePlayoutType_ScheduleKind.Designer.cs
  33. 28
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250823133952_Rename_ProgramSchedulePlayoutType_ScheduleKind.cs
  34. 2
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  35. 10
      ErsatzTV.Infrastructure/Scheduling/SequentialScheduleValidator.cs
  36. 2
      ErsatzTV.Infrastructure/Streaming/ExternalJsonPlayoutItemProvider.cs
  37. 4
      ErsatzTV/ErsatzTV.csproj
  38. 32
      ErsatzTV/Pages/PlayoutEditor.razor
  39. 65
      ErsatzTV/Pages/Playouts.razor
  40. 16
      ErsatzTV/Pages/SequentialPlayoutEditor.razor
  41. 6
      ErsatzTV/Pages/YamlValidator.razor
  42. 2
      ErsatzTV/PlayoutKind.cs
  43. 6
      ErsatzTV/Resources/sequential-schedule-import.schema.json
  44. 6
      ErsatzTV/Resources/sequential-schedule.schema.json
  45. 4
      ErsatzTV/Services/RunOnce/ResourceExtractorService.cs
  46. 2
      ErsatzTV/Startup.cs
  47. 6
      ErsatzTV/ViewModels/PlayoutEditViewModel.cs

8
CHANGELOG.md

@ -78,6 +78,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -78,6 +78,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix error when changing default (lowest priority) alternate schedule
### Changed
- Rename some schedule and playout terms for clarity
- Schedules are used to build playouts and are what actually differs
- The playout is the end result, and is the same no matter what schedule kind is used
- Supported schedule kinds:
- `Classic Schedules`
- `Block Schedules`
- `Sequential Schedules` (formerly `YAML Schedules` or `YAML Playouts`)
- `JSON (dizqueTV) Schedules` (formerly `External JSON Playouts`)
- Allow multiple watermarks in playback troubleshooting
- Classic schedules: allow selecting multiple watermarks on schedule items
- Block schedules: allow selecting multiple watermarks on decos

10
ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs

@ -190,10 +190,10 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -190,10 +190,10 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
foreach (Playout playout in playouts)
{
switch (playout.ProgramSchedulePlayoutType)
switch (playout.ScheduleKind)
{
case ProgramSchedulePlayoutType.Classic:
case ProgramSchedulePlayoutType.Yaml:
case PlayoutScheduleKind.Classic:
case PlayoutScheduleKind.Sequential:
var floodSorted = playouts
.Collect(p => p.Items)
.OrderBy(pi => pi.Start)
@ -211,7 +211,7 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -211,7 +211,7 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
minifier,
xml);
break;
case ProgramSchedulePlayoutType.Block:
case PlayoutScheduleKind.Block:
var blockSorted = playouts
.Collect(p => p.Items)
.OrderBy(pi => pi.Start)
@ -229,7 +229,7 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -229,7 +229,7 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
minifier,
xml);
break;
case ProgramSchedulePlayoutType.ExternalJson:
case PlayoutScheduleKind.ExternalJson:
var externalJsonSorted = (await CollectExternalJsonItems(playout.ExternalJsonFile))
.Filter(pi => pi.StartOffset <= finish)
.ToList();

2
ErsatzTV.Application/MediaCollections/Commands/PreviewPlaylistPlayoutHandler.cs

@ -32,7 +32,7 @@ public class PreviewPlaylistPlayoutHandler( @@ -32,7 +32,7 @@ public class PreviewPlaylistPlayoutHandler(
Name = "Playlist Preview"
},
Items = [],
ProgramSchedulePlayoutType = ProgramSchedulePlayoutType.Classic,
ScheduleKind = PlayoutScheduleKind.Classic,
PlayoutHistory = [],
ProgramSchedule = new ProgramSchedule
{

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

@ -108,14 +108,14 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -108,14 +108,14 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
PlayoutReferenceData referenceData = await GetReferenceData(
dbContext,
playout.Id,
playout.ProgramSchedulePlayoutType);
playout.ScheduleKind);
string channelNumber = referenceData.Channel.Number;
channelName = referenceData.Channel.Name;
PlayoutBuildResult result = PlayoutBuildResult.Empty;
switch (playout.ProgramSchedulePlayoutType)
switch (playout.ScheduleKind)
{
case ProgramSchedulePlayoutType.Block:
case PlayoutScheduleKind.Block:
result = await _blockPlayoutBuilder.Build(playout, referenceData, request.Mode, cancellationToken);
result = await _blockPlayoutFillerBuilder.Build(
playout,
@ -124,7 +124,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -124,7 +124,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
request.Mode,
cancellationToken);
break;
case ProgramSchedulePlayoutType.Yaml:
case PlayoutScheduleKind.Sequential:
result = await _yamlPlayoutBuilder.Build(
request.Start,
playout,
@ -132,11 +132,11 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -132,11 +132,11 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
request.Mode,
cancellationToken);
break;
case ProgramSchedulePlayoutType.ExternalJson:
case PlayoutScheduleKind.ExternalJson:
await _externalJsonPlayoutBuilder.Build(playout, request.Mode, cancellationToken);
break;
case ProgramSchedulePlayoutType.None:
case ProgramSchedulePlayoutType.Classic:
case PlayoutScheduleKind.None:
case PlayoutScheduleKind.Classic:
default:
result = await _playoutBuilder.Build(playout, referenceData, request.Mode, cancellationToken);
break;
@ -223,7 +223,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -223,7 +223,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
string fileName = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, $"{channelNumber}.xml");
if (hasChanges || !File.Exists(fileName) ||
playout.ProgramSchedulePlayoutType is ProgramSchedulePlayoutType.ExternalJson)
playout.ScheduleKind is PlayoutScheduleKind.ExternalJson)
{
await _workerChannel.WriteAsync(new RefreshChannelData(channelNumber), cancellationToken);
}
@ -276,9 +276,9 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -276,9 +276,9 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
foreach (Playout playout in maybePlayout)
{
switch (playout.ProgramSchedulePlayoutType)
switch (playout.ScheduleKind)
{
case ProgramSchedulePlayoutType.Classic:
case PlayoutScheduleKind.Classic:
await dbContext.Entry(playout)
.Collection(p => p.FillGroupIndices)
.LoadAsync();
@ -311,7 +311,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -311,7 +311,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
private static async Task<PlayoutReferenceData> GetReferenceData(
TvContext dbContext,
int playoutId,
ProgramSchedulePlayoutType playoutType)
PlayoutScheduleKind scheduleKind)
{
Channel channel = await dbContext.Channels
.AsNoTracking()
@ -322,7 +322,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -322,7 +322,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
List<PlayoutItem> existingItems = [];
List<PlayoutTemplate> playoutTemplates = [];
if (playoutType is ProgramSchedulePlayoutType.Block)
if (scheduleKind is PlayoutScheduleKind.Block)
{
deco = await dbContext.Decos
.AsNoTracking()

14
ErsatzTV.Application/Playouts/Commands/CreateBlockPlayoutHandler.cs

@ -41,11 +41,11 @@ public class CreateBlockPlayoutHandler( @@ -41,11 +41,11 @@ public class CreateBlockPlayoutHandler(
private static async Task<Validation<BaseError, Playout>> Validate(
TvContext dbContext,
CreateBlockPlayout request) =>
(await ValidateChannel(dbContext, request), ValidatePlayoutType(request))
.Apply((channel, playoutType) => new Playout
(await ValidateChannel(dbContext, request), ValidateScheduleKind(request))
.Apply((channel, scheduleKind) => new Playout
{
ChannelId = channel.Id,
ProgramSchedulePlayoutType = playoutType,
ScheduleKind = scheduleKind,
Seed = new Random().Next()
});
@ -64,9 +64,9 @@ public class CreateBlockPlayoutHandler( @@ -64,9 +64,9 @@ public class CreateBlockPlayoutHandler(
.Map(_ => channel)
.ToValidation<BaseError>("Channel already has one playout");
private static Validation<BaseError, ProgramSchedulePlayoutType> ValidatePlayoutType(
private static Validation<BaseError, PlayoutScheduleKind> ValidateScheduleKind(
CreateBlockPlayout createBlockPlayout) =>
Optional(createBlockPlayout.ProgramSchedulePlayoutType)
.Filter(playoutType => playoutType == ProgramSchedulePlayoutType.Block)
.ToValidation<BaseError>("[ProgramSchedulePlayoutType] must be Block");
Optional(createBlockPlayout.ScheduleKind)
.Filter(scheduleKind => scheduleKind == PlayoutScheduleKind.Block)
.ToValidation<BaseError>("[ScheduleKind] must be Block");
}

32
ErsatzTV.Application/Playouts/Commands/CreateFloodPlayoutHandler.cs → ErsatzTV.Application/Playouts/Commands/CreateClassicPlayoutHandler.cs

@ -10,12 +10,12 @@ using Channel = ErsatzTV.Core.Domain.Channel; @@ -10,12 +10,12 @@ using Channel = ErsatzTV.Core.Domain.Channel;
namespace ErsatzTV.Application.Playouts;
public class CreateFloodPlayoutHandler : IRequestHandler<CreateFloodPlayout, Either<BaseError, CreatePlayoutResponse>>
public class CreateClassicPlayoutHandler : IRequestHandler<CreateClassicPlayout, Either<BaseError, CreatePlayoutResponse>>
{
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
private readonly IDbContextFactory<TvContext> _dbContextFactory;
public CreateFloodPlayoutHandler(
public CreateClassicPlayoutHandler(
ChannelWriter<IBackgroundServiceRequest> channel,
IDbContextFactory<TvContext> dbContextFactory)
{
@ -24,7 +24,7 @@ public class CreateFloodPlayoutHandler : IRequestHandler<CreateFloodPlayout, Eit @@ -24,7 +24,7 @@ public class CreateFloodPlayoutHandler : IRequestHandler<CreateFloodPlayout, Eit
}
public async Task<Either<BaseError, CreatePlayoutResponse>> Handle(
CreateFloodPlayout request,
CreateClassicPlayout request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
@ -48,22 +48,22 @@ public class CreateFloodPlayoutHandler : IRequestHandler<CreateFloodPlayout, Eit @@ -48,22 +48,22 @@ public class CreateFloodPlayoutHandler : IRequestHandler<CreateFloodPlayout, Eit
private static async Task<Validation<BaseError, Playout>> Validate(
TvContext dbContext,
CreateFloodPlayout request) =>
CreateClassicPlayout request) =>
(await ValidateChannel(dbContext, request), await ValidateProgramSchedule(dbContext, request),
ValidatePlayoutType(request))
.Apply((channel, programSchedule, playoutType) => new Playout
ValidateScheduleKind(request))
.Apply((channel, programSchedule, scheduleKind) => new Playout
{
ChannelId = channel.Id,
ProgramScheduleId = programSchedule.Id,
ProgramSchedulePlayoutType = playoutType
ScheduleKind = scheduleKind
});
private static Task<Validation<BaseError, Channel>> ValidateChannel(
TvContext dbContext,
CreateFloodPlayout createFloodPlayout) =>
CreateClassicPlayout createClassicPlayout) =>
dbContext.Channels
.Include(c => c.Playouts)
.SelectOneAsync(c => c.Id, c => c.Id == createFloodPlayout.ChannelId)
.SelectOneAsync(c => c.Id, c => c.Id == createClassicPlayout.ChannelId)
.Map(o => o.ToValidation<BaseError>("Channel does not exist"))
.BindT(ChannelMustNotHavePlayouts);
@ -75,10 +75,10 @@ public class CreateFloodPlayoutHandler : IRequestHandler<CreateFloodPlayout, Eit @@ -75,10 +75,10 @@ public class CreateFloodPlayoutHandler : IRequestHandler<CreateFloodPlayout, Eit
private static Task<Validation<BaseError, ProgramSchedule>> ValidateProgramSchedule(
TvContext dbContext,
CreateFloodPlayout createFloodPlayout) =>
CreateClassicPlayout createClassicPlayout) =>
dbContext.ProgramSchedules
.Include(ps => ps.Items)
.SelectOneAsync(ps => ps.Id, ps => ps.Id == createFloodPlayout.ProgramScheduleId)
.SelectOneAsync(ps => ps.Id, ps => ps.Id == createClassicPlayout.ProgramScheduleId)
.Map(o => o.ToValidation<BaseError>("Program schedule does not exist"))
.BindT(ProgramScheduleMustHaveItems);
@ -88,9 +88,9 @@ public class CreateFloodPlayoutHandler : IRequestHandler<CreateFloodPlayout, Eit @@ -88,9 +88,9 @@ public class CreateFloodPlayoutHandler : IRequestHandler<CreateFloodPlayout, Eit
.Filter(ps => ps.Items.Count != 0)
.ToValidation<BaseError>("Program schedule must have items");
private static Validation<BaseError, ProgramSchedulePlayoutType> ValidatePlayoutType(
CreateFloodPlayout createFloodPlayout) =>
Optional(createFloodPlayout.ProgramSchedulePlayoutType)
.Filter(playoutType => playoutType != ProgramSchedulePlayoutType.None)
.ToValidation<BaseError>("[ProgramSchedulePlayoutType] must not be None");
private static Validation<BaseError, PlayoutScheduleKind> ValidateScheduleKind(
CreateClassicPlayout createClassicPlayout) =>
Optional(createClassicPlayout.ScheduleKind)
.Filter(scheduleKind => scheduleKind == PlayoutScheduleKind.Classic)
.ToValidation<BaseError>("[ScheduleKind] must be Classic");
}

14
ErsatzTV.Application/Playouts/Commands/CreateExternalJsonPlayoutHandler.cs

@ -49,12 +49,12 @@ public class CreateExternalJsonPlayoutHandler @@ -49,12 +49,12 @@ public class CreateExternalJsonPlayoutHandler
private async Task<Validation<BaseError, Playout>> Validate(
TvContext dbContext,
CreateExternalJsonPlayout request) =>
(await ValidateChannel(dbContext, request), ValidateExternalJsonFile(request), ValidatePlayoutType(request))
.Apply((channel, externalJsonFile, playoutType) => new Playout
(await ValidateChannel(dbContext, request), ValidateExternalJsonFile(request), ValidateScheduleKind(request))
.Apply((channel, externalJsonFile, scheduleKind) => new Playout
{
ChannelId = channel.Id,
ExternalJsonFile = externalJsonFile,
ProgramSchedulePlayoutType = playoutType
ScheduleKind = scheduleKind
});
private static Task<Validation<BaseError, Channel>> ValidateChannel(
@ -82,9 +82,9 @@ public class CreateExternalJsonPlayoutHandler @@ -82,9 +82,9 @@ public class CreateExternalJsonPlayoutHandler
return request.ExternalJsonFile;
}
private static Validation<BaseError, ProgramSchedulePlayoutType> ValidatePlayoutType(
private static Validation<BaseError, PlayoutScheduleKind> ValidateScheduleKind(
CreateExternalJsonPlayout createExternalJsonPlayout) =>
Optional(createExternalJsonPlayout.ProgramSchedulePlayoutType)
.Filter(playoutType => playoutType == ProgramSchedulePlayoutType.ExternalJson)
.ToValidation<BaseError>("[ProgramSchedulePlayoutType] must be ExternalJson");
Optional(createExternalJsonPlayout.ScheduleKind)
.Filter(scheduleKind => scheduleKind == PlayoutScheduleKind.ExternalJson)
.ToValidation<BaseError>("[ScheduleKind] must be ExternalJson");
}

14
ErsatzTV.Application/Playouts/Commands/CreatePlayout.cs

@ -3,17 +3,17 @@ using ErsatzTV.Core.Domain; @@ -3,17 +3,17 @@ using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.Playouts;
public record CreatePlayout(int ChannelId, ProgramSchedulePlayoutType ProgramSchedulePlayoutType)
public record CreatePlayout(int ChannelId, PlayoutScheduleKind ScheduleKind)
: IRequest<Either<BaseError, CreatePlayoutResponse>>;
public record CreateFloodPlayout(int ChannelId, int ProgramScheduleId)
: CreatePlayout(ChannelId, ProgramSchedulePlayoutType.Classic);
public record CreateClassicPlayout(int ChannelId, int ProgramScheduleId)
: CreatePlayout(ChannelId, PlayoutScheduleKind.Classic);
public record CreateBlockPlayout(int ChannelId)
: CreatePlayout(ChannelId, ProgramSchedulePlayoutType.Block);
: CreatePlayout(ChannelId, PlayoutScheduleKind.Block);
public record CreateYamlPlayout(int ChannelId, string TemplateFile)
: CreatePlayout(ChannelId, ProgramSchedulePlayoutType.Yaml);
public record CreateSequentialPlayout(int ChannelId, string TemplateFile)
: CreatePlayout(ChannelId, PlayoutScheduleKind.Sequential);
public record CreateExternalJsonPlayout(int ChannelId, string ExternalJsonFile)
: CreatePlayout(ChannelId, ProgramSchedulePlayoutType.ExternalJson);
: CreatePlayout(ChannelId, PlayoutScheduleKind.ExternalJson);

34
ErsatzTV.Application/Playouts/Commands/CreateYamlPlayoutHandler.cs → ErsatzTV.Application/Playouts/Commands/CreateSequentialPlayoutHandler.cs

@ -11,14 +11,14 @@ using Channel = ErsatzTV.Core.Domain.Channel; @@ -11,14 +11,14 @@ using Channel = ErsatzTV.Core.Domain.Channel;
namespace ErsatzTV.Application.Playouts;
public class CreateYamlPlayoutHandler
: IRequestHandler<CreateYamlPlayout, Either<BaseError, CreatePlayoutResponse>>
public class CreateSequentialPlayoutHandler
: IRequestHandler<CreateSequentialPlayout, Either<BaseError, CreatePlayoutResponse>>
{
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
private readonly IDbContextFactory<TvContext> _dbContextFactory;
private readonly ILocalFileSystem _localFileSystem;
public CreateYamlPlayoutHandler(
public CreateSequentialPlayoutHandler(
ILocalFileSystem localFileSystem,
ChannelWriter<IBackgroundServiceRequest> channel,
IDbContextFactory<TvContext> dbContextFactory)
@ -29,7 +29,7 @@ public class CreateYamlPlayoutHandler @@ -29,7 +29,7 @@ public class CreateYamlPlayoutHandler
}
public async Task<Either<BaseError, CreatePlayoutResponse>> Handle(
CreateYamlPlayout request,
CreateSequentialPlayout request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
@ -53,22 +53,22 @@ public class CreateYamlPlayoutHandler @@ -53,22 +53,22 @@ public class CreateYamlPlayoutHandler
private async Task<Validation<BaseError, Playout>> Validate(
TvContext dbContext,
CreateYamlPlayout request) =>
(await ValidateChannel(dbContext, request), ValidateYamlFile(request), ValidatePlayoutType(request))
.Apply((channel, externalJsonFile, playoutType) => new Playout
CreateSequentialPlayout request) =>
(await ValidateChannel(dbContext, request), ValidateYamlFile(request), ValidateScheduleKind(request))
.Apply((channel, yamlFile, scheduleKind) => new Playout
{
ChannelId = channel.Id,
TemplateFile = externalJsonFile,
ProgramSchedulePlayoutType = playoutType,
TemplateFile = yamlFile,
ScheduleKind = scheduleKind,
Seed = new Random().Next()
});
private static Task<Validation<BaseError, Channel>> ValidateChannel(
TvContext dbContext,
CreateYamlPlayout createYamlPlayout) =>
CreateSequentialPlayout createSequentialPlayout) =>
dbContext.Channels
.Include(c => c.Playouts)
.SelectOneAsync(c => c.Id, c => c.Id == createYamlPlayout.ChannelId)
.SelectOneAsync(c => c.Id, c => c.Id == createSequentialPlayout.ChannelId)
.Map(o => o.ToValidation<BaseError>("Channel does not exist"))
.BindT(ChannelMustNotHavePlayouts);
@ -78,7 +78,7 @@ public class CreateYamlPlayoutHandler @@ -78,7 +78,7 @@ public class CreateYamlPlayoutHandler
.Map(_ => channel)
.ToValidation<BaseError>("Channel already has one playout");
private Validation<BaseError, string> ValidateYamlFile(CreateYamlPlayout request)
private Validation<BaseError, string> ValidateYamlFile(CreateSequentialPlayout request)
{
if (!_localFileSystem.FileExists(request.TemplateFile))
{
@ -88,9 +88,9 @@ public class CreateYamlPlayoutHandler @@ -88,9 +88,9 @@ public class CreateYamlPlayoutHandler
return request.TemplateFile;
}
private static Validation<BaseError, ProgramSchedulePlayoutType> ValidatePlayoutType(
CreateYamlPlayout createYamlPlayout) =>
Optional(createYamlPlayout.ProgramSchedulePlayoutType)
.Filter(playoutType => playoutType == ProgramSchedulePlayoutType.Yaml)
.ToValidation<BaseError>("[ProgramSchedulePlayoutType] must be YAML");
private static Validation<BaseError, PlayoutScheduleKind> ValidateScheduleKind(
CreateSequentialPlayout createSequentialPlayout) =>
Optional(createSequentialPlayout.ScheduleKind)
.Filter(scheduleKind => scheduleKind == PlayoutScheduleKind.Sequential)
.ToValidation<BaseError>("[ScheduleKind] must be YAML");
}

12
ErsatzTV.Application/Playouts/Commands/ResetAllPlayoutsHandler.cs

@ -19,11 +19,11 @@ public class ResetAllPlayoutsHandler( @@ -19,11 +19,11 @@ public class ResetAllPlayoutsHandler(
foreach (Playout playout in await dbContext.Playouts.ToListAsync(cancellationToken))
{
switch (playout.ProgramSchedulePlayoutType)
switch (playout.ScheduleKind)
{
case ProgramSchedulePlayoutType.Classic:
case ProgramSchedulePlayoutType.Block:
case ProgramSchedulePlayoutType.Yaml:
case PlayoutScheduleKind.Classic:
case PlayoutScheduleKind.Block:
case PlayoutScheduleKind.Sequential:
if (!locker.IsPlayoutLocked(playout.Id))
{
await channel.WriteAsync(
@ -32,8 +32,8 @@ public class ResetAllPlayoutsHandler( @@ -32,8 +32,8 @@ public class ResetAllPlayoutsHandler(
}
break;
case ProgramSchedulePlayoutType.ExternalJson:
case ProgramSchedulePlayoutType.None:
case PlayoutScheduleKind.ExternalJson:
case PlayoutScheduleKind.None:
default:
// external json cannot be reset
continue;

2
ErsatzTV.Application/Playouts/Commands/UpdateExternalJsonPlayoutHandler.cs

@ -46,7 +46,7 @@ public class @@ -46,7 +46,7 @@ public class
return new PlayoutNameViewModel(
playout.Id,
playout.ProgramSchedulePlayoutType,
playout.ScheduleKind,
playout.Channel.Name,
playout.Channel.Number,
playout.Channel.PlayoutMode,

2
ErsatzTV.Application/Playouts/Commands/UpdatePlayoutHandler.cs

@ -38,7 +38,7 @@ public class UpdatePlayoutHandler : IRequestHandler<UpdatePlayout, Either<BaseEr @@ -38,7 +38,7 @@ public class UpdatePlayoutHandler : IRequestHandler<UpdatePlayout, Either<BaseEr
return new PlayoutNameViewModel(
playout.Id,
playout.ProgramSchedulePlayoutType,
playout.ScheduleKind,
playout.Channel.Name,
playout.Channel.Number,
playout.Channel.PlayoutMode,

2
ErsatzTV.Application/Playouts/Commands/UpdateYamlPlayout.cs → ErsatzTV.Application/Playouts/Commands/UpdateSequentialPlayout.cs

@ -2,5 +2,5 @@ @@ -2,5 +2,5 @@
namespace ErsatzTV.Application.Playouts;
public record UpdateYamlPlayout(int PlayoutId, string TemplateFile)
public record UpdateSequentialPlayout(int PlayoutId, string TemplateFile)
: IRequest<Either<BaseError, PlayoutNameViewModel>>;

14
ErsatzTV.Application/Playouts/Commands/UpdateTemplatePlayoutHandler.cs → ErsatzTV.Application/Playouts/Commands/UpdateSequentialPlayoutHandler.cs

@ -9,13 +9,13 @@ using Microsoft.EntityFrameworkCore; @@ -9,13 +9,13 @@ using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.Playouts;
public class
UpdateTemplatePlayoutHandler : IRequestHandler<UpdateYamlPlayout,
UpdateSequentialPlayoutHandler : IRequestHandler<UpdateSequentialPlayout,
Either<BaseError, PlayoutNameViewModel>>
{
private readonly IDbContextFactory<TvContext> _dbContextFactory;
private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel;
public UpdateTemplatePlayoutHandler(
public UpdateSequentialPlayoutHandler(
IDbContextFactory<TvContext> dbContextFactory,
ChannelWriter<IBackgroundServiceRequest> workerChannel)
{
@ -24,7 +24,7 @@ public class @@ -24,7 +24,7 @@ public class
}
public async Task<Either<BaseError, PlayoutNameViewModel>> Handle(
UpdateYamlPlayout request,
UpdateSequentialPlayout request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
@ -34,7 +34,7 @@ public class @@ -34,7 +34,7 @@ public class
private async Task<PlayoutNameViewModel> ApplyUpdateRequest(
TvContext dbContext,
UpdateYamlPlayout request,
UpdateSequentialPlayout request,
Playout playout)
{
playout.TemplateFile = request.TemplateFile;
@ -46,7 +46,7 @@ public class @@ -46,7 +46,7 @@ public class
return new PlayoutNameViewModel(
playout.Id,
playout.ProgramSchedulePlayoutType,
playout.ScheduleKind,
playout.Channel.Name,
playout.Channel.Number,
playout.Channel.PlayoutMode,
@ -58,12 +58,12 @@ public class @@ -58,12 +58,12 @@ public class
private static Task<Validation<BaseError, Playout>> Validate(
TvContext dbContext,
UpdateYamlPlayout request) =>
UpdateSequentialPlayout request) =>
PlayoutMustExist(dbContext, request);
private static Task<Validation<BaseError, Playout>> PlayoutMustExist(
TvContext dbContext,
UpdateYamlPlayout updatePlayout) =>
UpdateSequentialPlayout updatePlayout) =>
dbContext.Playouts
.Include(p => p.Channel)
.SelectOneAsync(p => p.Id, p => p.Id == updatePlayout.PlayoutId)

2
ErsatzTV.Application/Playouts/PlayoutNameViewModel.cs

@ -4,7 +4,7 @@ namespace ErsatzTV.Application.Playouts; @@ -4,7 +4,7 @@ namespace ErsatzTV.Application.Playouts;
public record PlayoutNameViewModel(
int PlayoutId,
ProgramSchedulePlayoutType PlayoutType,
PlayoutScheduleKind ScheduleKind,
string ChannelName,
string ChannelNumber,
ChannelPlayoutMode PlayoutMode,

2
ErsatzTV.Application/Playouts/PlayoutViewModel.cs

@ -6,4 +6,4 @@ public record PlayoutViewModel( @@ -6,4 +6,4 @@ public record PlayoutViewModel(
int Id,
PlayoutChannelViewModel Channel,
PlayoutProgramScheduleViewModel ProgramSchedule,
ProgramSchedulePlayoutType ProgramSchedulePlayoutType);
PlayoutScheduleKind ScheduleKind);

2
ErsatzTV.Application/Playouts/Queries/GetAllPlayoutsHandler.cs

@ -21,7 +21,7 @@ public class GetAllPlayoutsHandler : IRequestHandler<GetAllPlayouts, List<Playou @@ -21,7 +21,7 @@ public class GetAllPlayoutsHandler : IRequestHandler<GetAllPlayouts, List<Playou
.Filter(p => p.Channel != null)
.Map(p => new PlayoutNameViewModel(
p.Id,
p.ProgramSchedulePlayoutType,
p.ScheduleKind,
p.Channel.Name,
p.Channel.Number,
p.Channel.PlayoutMode,

2
ErsatzTV.Application/Playouts/Queries/GetPlayoutByIdHandler.cs

@ -19,7 +19,7 @@ public class GetPlayoutByIdHandler(IDbContextFactory<TvContext> dbContextFactory @@ -19,7 +19,7 @@ public class GetPlayoutByIdHandler(IDbContextFactory<TvContext> dbContextFactory
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId)
.MapT(p => new PlayoutNameViewModel(
p.Id,
p.ProgramSchedulePlayoutType,
p.ScheduleKind,
p.Channel.Name,
p.Channel.Number,
p.Channel.PlayoutMode,

4
ErsatzTV.Application/Scheduling/Commands/ErasePlayoutHistoryHandler.cs

@ -13,8 +13,8 @@ public class ErasePlayoutHistoryHandler(IDbContextFactory<TvContext> dbContextFa @@ -13,8 +13,8 @@ public class ErasePlayoutHistoryHandler(IDbContextFactory<TvContext> dbContextFa
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
Option<Playout> maybePlayout = await dbContext.Playouts
.Filter(p => p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Block ||
p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Yaml)
.Filter(p => p.ScheduleKind == PlayoutScheduleKind.Block ||
p.ScheduleKind == PlayoutScheduleKind.Sequential)
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId);
foreach (Playout playout in maybePlayout)

4
ErsatzTV.Application/Scheduling/Commands/ErasePlayoutItemsHandler.cs

@ -16,8 +16,8 @@ public class ErasePlayoutItemsHandler(IDbContextFactory<TvContext> dbContextFact @@ -16,8 +16,8 @@ public class ErasePlayoutItemsHandler(IDbContextFactory<TvContext> dbContextFact
Option<Playout> maybePlayout = await dbContext.Playouts
.Include(p => p.Items)
.Include(p => p.PlayoutHistory)
.Filter(p => p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Block ||
p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Yaml)
.Filter(p => p.ScheduleKind == PlayoutScheduleKind.Block ||
p.ScheduleKind == PlayoutScheduleKind.Sequential)
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId);
foreach (Playout playout in maybePlayout)

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

@ -42,7 +42,7 @@ public class PreviewBlockPlayoutHandler( @@ -42,7 +42,7 @@ public class PreviewBlockPlayoutHandler(
Name = "Block Preview"
},
Items = [],
ProgramSchedulePlayoutType = ProgramSchedulePlayoutType.Block,
ScheduleKind = PlayoutScheduleKind.Block,
PlayoutHistory = [],
Templates =
[

12
ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs

@ -133,7 +133,7 @@ public class ScheduleIntegrationTests @@ -133,7 +133,7 @@ public class ScheduleIntegrationTests
PlayoutReferenceData referenceData = await GetReferenceData(
context,
PLAYOUT_ID,
ProgramSchedulePlayoutType.Classic);
PlayoutScheduleKind.Classic);
await builder.Build(
playout,
@ -157,7 +157,7 @@ public class ScheduleIntegrationTests @@ -157,7 +157,7 @@ public class ScheduleIntegrationTests
PlayoutReferenceData referenceData = await GetReferenceData(
context,
PLAYOUT_ID,
ProgramSchedulePlayoutType.Classic);
PlayoutScheduleKind.Classic);
await builder.Build(
playout,
@ -181,7 +181,7 @@ public class ScheduleIntegrationTests @@ -181,7 +181,7 @@ public class ScheduleIntegrationTests
PlayoutReferenceData referenceData = await GetReferenceData(
context,
PLAYOUT_ID,
ProgramSchedulePlayoutType.Classic);
PlayoutScheduleKind.Classic);
await builder.Build(
playout,
@ -329,7 +329,7 @@ public class ScheduleIntegrationTests @@ -329,7 +329,7 @@ public class ScheduleIntegrationTests
PlayoutReferenceData referenceData = await GetReferenceData(
context,
playoutId,
ProgramSchedulePlayoutType.Classic);
PlayoutScheduleKind.Classic);
await builder.Build(
playout,
@ -397,7 +397,7 @@ public class ScheduleIntegrationTests @@ -397,7 +397,7 @@ public class ScheduleIntegrationTests
private static async Task<PlayoutReferenceData> GetReferenceData(
TvContext dbContext,
int playoutId,
ProgramSchedulePlayoutType playoutType)
PlayoutScheduleKind scheduleKind)
{
Channel channel = await dbContext.Channels
.AsNoTracking()
@ -407,7 +407,7 @@ public class ScheduleIntegrationTests @@ -407,7 +407,7 @@ public class ScheduleIntegrationTests
List<PlayoutItem> existingItems = [];
List<PlayoutTemplate> playoutTemplates = [];
if (playoutType is ProgramSchedulePlayoutType.Block)
if (scheduleKind is PlayoutScheduleKind.Block)
{
existingItems = await dbContext.PlayoutItems
.AsNoTracking()

2
ErsatzTV.Core/Domain/Playout.cs

@ -12,7 +12,7 @@ public class Playout @@ -12,7 +12,7 @@ public class Playout
public string ExternalJsonFile { get; set; }
public string TemplateFile { get; set; }
public List<ProgramScheduleAlternate> ProgramScheduleAlternates { get; set; }
public ProgramSchedulePlayoutType ProgramSchedulePlayoutType { get; set; }
public PlayoutScheduleKind ScheduleKind { get; set; }
public List<PlayoutItem> Items { get; set; }
public PlayoutAnchor Anchor { get; set; }
public List<PlayoutProgramScheduleAnchor> ProgramScheduleAnchors { get; set; }

4
ErsatzTV.Core/Domain/ProgramSchedulePlayoutType.cs → ErsatzTV.Core/Domain/PlayoutScheduleKind.cs

@ -1,11 +1,11 @@ @@ -1,11 +1,11 @@
namespace ErsatzTV.Core.Domain;
public enum ProgramSchedulePlayoutType
public enum PlayoutScheduleKind
{
None = 0,
Classic = 1,
Block = 2,
Yaml = 3,
Sequential = 3,
ExternalJson = 20
}

2
ErsatzTV.Core/Interfaces/Scheduling/IYamlScheduleValidator.cs → ErsatzTV.Core/Interfaces/Scheduling/ISequentialScheduleValidator.cs

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
namespace ErsatzTV.Core.Interfaces.Scheduling;
public interface IYamlScheduleValidator
public interface ISequentialScheduleValidator
{
Task<bool> ValidateSchedule(string yaml, bool isImport);
string ToJson(string yaml);

4
ErsatzTV.Core/Scheduling/PlayoutBuilder.cs

@ -67,11 +67,11 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -67,11 +67,11 @@ public class PlayoutBuilder : IPlayoutBuilder
{
PlayoutBuildResult result = PlayoutBuildResult.Empty;
if (playout.ProgramSchedulePlayoutType is not ProgramSchedulePlayoutType.Classic)
if (playout.ScheduleKind is not PlayoutScheduleKind.Classic)
{
_logger.LogWarning(
"Skipping playout build with type {Type} on channel {Number} - {Name}",
playout.ProgramSchedulePlayoutType,
playout.ScheduleKind,
referenceData.Channel.Number,
referenceData.Channel.Name);

2
ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutRepeatHandler.cs

@ -25,7 +25,7 @@ public class YamlPlayoutRepeatHandler : IYamlPlayoutHandler @@ -25,7 +25,7 @@ public class YamlPlayoutRepeatHandler : IYamlPlayoutHandler
if (context.VisitedAll && _itemsSinceLastRepeat == context.AddedItems.Count)
{
logger.LogWarning("Repeat encountered without adding any playout items; aborting");
throw new InvalidOperationException("YAML playout loop detected");
throw new InvalidOperationException("Sequential playout loop detected");
}
_itemsSinceLastRepeat = context.AddedItems.Count;

4
ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs

@ -20,7 +20,7 @@ public class YamlPlayoutBuilder( @@ -20,7 +20,7 @@ public class YamlPlayoutBuilder(
IMediaCollectionRepository mediaCollectionRepository,
IChannelRepository channelRepository,
IGraphicsElementRepository graphicsElementRepository,
IYamlScheduleValidator yamlScheduleValidator,
ISequentialScheduleValidator sequentialScheduleValidator,
ILogger<YamlPlayoutBuilder> logger)
: IYamlPlayoutBuilder
{
@ -455,7 +455,7 @@ public class YamlPlayoutBuilder( @@ -455,7 +455,7 @@ public class YamlPlayoutBuilder(
try
{
string yaml = await File.ReadAllTextAsync(fileName, cancellationToken);
if (!await yamlScheduleValidator.ValidateSchedule(yaml, isImport))
if (!await sequentialScheduleValidator.ValidateSchedule(yaml, isImport))
{
return Option<YamlPlayoutDefinition>.None;
}

6331
ErsatzTV.Infrastructure.MySql/Migrations/20250823133839_Rename_ProgramSchedulePlayoutType_ScheduleKind.Designer.cs generated

File diff suppressed because it is too large Load Diff

28
ErsatzTV.Infrastructure.MySql/Migrations/20250823133839_Rename_ProgramSchedulePlayoutType_ScheduleKind.cs

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Rename_ProgramSchedulePlayoutType_ScheduleKind : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "ProgramSchedulePlayoutType",
table: "Playout",
newName: "ScheduleKind");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "ScheduleKind",
table: "Playout",
newName: "ProgramSchedulePlayoutType");
}
}
}

2
ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs

@ -1831,7 +1831,7 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -1831,7 +1831,7 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int?>("ProgramScheduleId")
.HasColumnType("int");
b.Property<int>("ProgramSchedulePlayoutType")
b.Property<int>("ScheduleKind")
.HasColumnType("int");
b.Property<int>("Seed")

6166
ErsatzTV.Infrastructure.Sqlite/Migrations/20250823133952_Rename_ProgramSchedulePlayoutType_ScheduleKind.Designer.cs generated

File diff suppressed because it is too large Load Diff

28
ErsatzTV.Infrastructure.Sqlite/Migrations/20250823133952_Rename_ProgramSchedulePlayoutType_ScheduleKind.cs

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Rename_ProgramSchedulePlayoutType_ScheduleKind : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "ProgramSchedulePlayoutType",
table: "Playout",
newName: "ScheduleKind");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "ScheduleKind",
table: "Playout",
newName: "ProgramSchedulePlayoutType");
}
}
}

2
ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs

@ -1742,7 +1742,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -1742,7 +1742,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int?>("ProgramScheduleId")
.HasColumnType("INTEGER");
b.Property<int>("ProgramSchedulePlayoutType")
b.Property<int>("ScheduleKind")
.HasColumnType("INTEGER");
b.Property<int>("Seed")

10
ErsatzTV.Infrastructure/Scheduling/YamlScheduleValidator.cs → ErsatzTV.Infrastructure/Scheduling/SequentialScheduleValidator.cs

@ -11,7 +11,7 @@ using JsonSerializer = System.Text.Json.JsonSerializer; @@ -11,7 +11,7 @@ using JsonSerializer = System.Text.Json.JsonSerializer;
namespace ErsatzTV.Infrastructure.Scheduling;
public class YamlScheduleValidator(ILogger<YamlScheduleValidator> logger) : IYamlScheduleValidator
public class SequentialScheduleValidator(ILogger<SequentialScheduleValidator> logger) : ISequentialScheduleValidator
{
public async Task<bool> ValidateSchedule(string yaml, bool isImport)
{
@ -19,7 +19,7 @@ public class YamlScheduleValidator(ILogger<YamlScheduleValidator> logger) : IYam @@ -19,7 +19,7 @@ public class YamlScheduleValidator(ILogger<YamlScheduleValidator> logger) : IYam
{
string schemaFileName = Path.Combine(
FileSystemLayout.ResourcesCacheFolder,
isImport ? "yaml-playout-import.schema.json" : "yaml-playout.schema.json");
isImport ? "sequential-schedule-import.schema.json" : "sequential-schedule.schema.json");
using StreamReader sr = File.OpenText(schemaFileName);
await using var reader = new JsonTextReader(sr);
var schema = JSchema.Load(reader);
@ -31,7 +31,7 @@ public class YamlScheduleValidator(ILogger<YamlScheduleValidator> logger) : IYam @@ -31,7 +31,7 @@ public class YamlScheduleValidator(ILogger<YamlScheduleValidator> logger) : IYam
if (!schedule.IsValid(schema, out IList<string> errorMessages))
{
logger.LogWarning("Failed to validate YAML schedule definition: {ErrorMessages}", errorMessages);
logger.LogWarning("Failed to validate sequential schedule definition: {ErrorMessages}", errorMessages);
return false;
}
@ -39,7 +39,7 @@ public class YamlScheduleValidator(ILogger<YamlScheduleValidator> logger) : IYam @@ -39,7 +39,7 @@ public class YamlScheduleValidator(ILogger<YamlScheduleValidator> logger) : IYam
}
catch (Exception ex)
{
logger.LogWarning(ex, "Unexpected error while validating YAML schedule definition");
logger.LogWarning(ex, "Unexpected error while validating sequential schedule definition");
}
return false;
@ -62,7 +62,7 @@ public class YamlScheduleValidator(ILogger<YamlScheduleValidator> logger) : IYam @@ -62,7 +62,7 @@ public class YamlScheduleValidator(ILogger<YamlScheduleValidator> logger) : IYam
{
string schemaFileName = Path.Combine(
FileSystemLayout.ResourcesCacheFolder,
isImport ? "yaml-playout-import.schema.json" : "yaml-playout.schema.json");
isImport ? "sequential-schedule-import.schema.json" : "sequential-schedule.schema.json");
using StreamReader sr = File.OpenText(schemaFileName);
await using var reader = new JsonTextReader(sr);
var schema = JSchema.Load(reader);

2
ErsatzTV.Infrastructure/Streaming/ExternalJsonPlayoutItemProvider.cs

@ -58,7 +58,7 @@ public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider @@ -58,7 +58,7 @@ public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider
foreach (Playout playout in maybePlayout)
{
// playout must be external json
if (playout.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.ExternalJson)
if (playout.ScheduleKind == PlayoutScheduleKind.ExternalJson)
{
// json file must exist
if (_localFileSystem.FileExists(playout.ExternalJsonFile))

4
ErsatzTV/ErsatzTV.csproj

@ -81,8 +81,8 @@ @@ -81,8 +81,8 @@
<EmbeddedResource Include="Resources\Templates\_musicVideo.sbntxt" />
<EmbeddedResource Include="Resources\Templates\_otherVideo.sbntxt" />
<EmbeddedResource Include="Resources\Templates\_song.sbntxt" />
<EmbeddedResource Include="Resources\yaml-playout-import.schema.json" />
<EmbeddedResource Include="Resources\yaml-playout.schema.json" />
<EmbeddedResource Include="Resources\sequential-schedule-import.schema.json" />
<EmbeddedResource Include="Resources\sequential-schedule.schema.json" />
</ItemGroup>
<ItemGroup>

32
ErsatzTV/Pages/PlayoutEditor.razor

@ -13,22 +13,8 @@ @@ -13,22 +13,8 @@
<MudForm @ref="_form" Validation="@(_validator.ValidateValue)" ValidationDelay="0" Style="max-height: 100%">
<MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100; align-items: center">
<MudButton Class="ml-6" OnClick="HandleSubmitAsync" Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add">
@switch (Kind)
{
case PlayoutKind.ExternalJson:
@:Add External Json Playout
break;
case PlayoutKind.Yaml:
@:Add YAML Playout
break;
case PlayoutKind.Block:
@:Add Block Playout
break;
default:
@:Add Playout
break;
}
<MudButton Class="ml-6" OnClick="@HandleSubmitAsync" Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add">
Add Playout
</MudButton>
</MudPaper>
<div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
@ -53,25 +39,29 @@ @@ -53,25 +39,29 @@
case PlayoutKind.ExternalJson:
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>External Json File</MudText>
<MudText>JSON (dizqueTV) Schedule</MudText>
</div>
<MudTextField @bind-Value="_model.ExternalJsonFile" For="@(() => _model.ExternalJsonFile)"/>
</MudStack>
break;
case PlayoutKind.Yaml:
case PlayoutKind.Sequential:
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>YAML File</MudText>
<MudText>Sequential Schedule</MudText>
</div>
<MudTextField @bind-Value="_model.YamlFile" For="@(() => _model.YamlFile)"/>
<MudTextField @bind-Value="_model.SequentialSchedule" For="@(() => _model.SequentialSchedule)"/>
</MudStack>
break;
case PlayoutKind.Block:
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"></div>
<MudText Typo="Typo.caption" Style="font-weight: normal">Block templates are added later</MudText>
</MudStack>
break;
default:
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Schedule</MudText>
<MudText>Classic Schedule</MudText>
</div>
<MudSelect T="ProgramScheduleViewModel" @bind-value="_model.ProgramSchedule">
@foreach (ProgramScheduleViewModel schedule in _programSchedules)

65
ErsatzTV/Pages/Playouts.razor

@ -17,24 +17,17 @@ @@ -17,24 +17,17 @@
<div style="display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%; align-items: center" class="ml-6 mr-6">
<div class="flex-grow-1"></div>
<div style="margin-left: auto" class="d-none d-md-flex">
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add" Href="playouts/add">
Add Playout
</MudButton>
<MudTooltip Text="This feature is experimental">
<MudButton Class="ml-3" Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Warning" Href="@($"playouts/add/{PlayoutKind.Block}")">
Add Block Playout
</MudButton>
</MudTooltip>
<MudTooltip Text="This feature is experimental">
<MudButton Class="ml-3" Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Warning" Href="@($"playouts/add/{PlayoutKind.Yaml}")">
Add YAML Playout
</MudButton>
</MudTooltip>
<MudTooltip Text="This feature is experimental">
<MudButton Class="ml-3" Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Warning" Href="@($"playouts/add/{PlayoutKind.ExternalJson}")">
Add External Json Playout
</MudButton>
</MudTooltip>
<MudMenu>
<ActivatorContent>
<MudButton StartIcon="@Icons.Material.Filled.Add" Variant="Variant.Filled" Color="Color.Primary">Add Playout</MudButton>
</ActivatorContent>
<ChildContent>
<MudMenuItem Icon="@Icons.Material.Filled.Add" Label="Classic Schedule" Href="playouts/add"/>
<MudMenuItem Icon="@Icons.Material.Filled.Add" Label="Block Schedule" Href="@($"playouts/add/{PlayoutKind.Block}")"/>
<MudMenuItem Icon="@Icons.Material.Filled.Add" Label="Sequential Schedule" Href="@($"playouts/add/{PlayoutKind.Sequential}")"/>
<MudMenuItem Icon="@Icons.Material.Filled.Warning" Label="JSON (dizqueTV) Schedule" Href="@($"playouts/add/{PlayoutKind.ExternalJson}")"/>
</ChildContent>
</MudMenu>
<MudButton Class="ml-3" Variant="Variant.Filled" Color="Color.Warning" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@ResetAllPlayouts">
Reset All Playouts
</MudButton>
@ -42,11 +35,11 @@ @@ -42,11 +35,11 @@
<div style="align-items: center; display: flex; margin-left: auto;" class="d-md-none">
<div class="flex-grow-1"></div>
<MudMenu Icon="@Icons.Material.Filled.MoreVert">
<MudMenuItem Icon="@Icons.Material.Filled.Add" Label="Add Playout" Href="playouts/add"/>
<MudMenuItem Icon="@Icons.Material.Filled.Warning" Label="Add Block Playout" Href="@($"playouts/add/{PlayoutKind.Block}")"/>
<MudMenuItem Icon="@Icons.Material.Filled.Warning" Label="Add YAML Playout" Href="@($"playouts/add/{PlayoutKind.Yaml}")"/>
<MudMenuItem Icon="@Icons.Material.Filled.Warning" Label="Add External Json Playout" Href="@($"playouts/add/{PlayoutKind.ExternalJson}")"/>
<MudMenuItem Icon="@Icons.Material.Filled.Refresh" Label="Reset All Playouts" OnClick="ResetAllPlayouts"/>
<MudMenuItem Icon="@Icons.Material.Filled.Add" Label="Add Classic Playout" Href="playouts/add"/>
<MudMenuItem Icon="@Icons.Material.Filled.Add" Label="Add Block Playout" Href="@($"playouts/add/{PlayoutKind.Block}")"/>
<MudMenuItem Icon="@Icons.Material.Filled.Add" Label="Add Sequential Playout" Href="@($"playouts/add/{PlayoutKind.Sequential}")"/>
<MudMenuItem Icon="@Icons.Material.Filled.Warning" Label="Add JSON (dizqueTV) Playout" Href="@($"playouts/add/{PlayoutKind.ExternalJson}")"/>
<MudMenuItem Icon="@Icons.Material.Filled.Refresh" Label="Reset All Playouts" OnClick="@ResetAllPlayouts"/>
</MudMenu>
</div>
</div>
@ -80,23 +73,23 @@ @@ -80,23 +73,23 @@
Default Schedule
</MudTableSortLabel>
</MudTh>
<MudTh Class="d-none d-md-table-cell">Playout Type</MudTh>
<MudTh Class="d-none d-md-table-cell">Schedule Kind</MudTh>
<MudTh/>
</HeaderContent>
<RowTemplate>
<MudTd>@context.ChannelNumber - @context.ChannelName</MudTd>
<MudTd Class="d-none d-md-table-cell">@context.ScheduleName</MudTd>
<MudTd Class="d-none d-md-table-cell">
@switch (context.PlayoutType)
@switch (context.ScheduleKind)
{
case ProgramSchedulePlayoutType.Block:
case PlayoutScheduleKind.Block:
<span>Block</span>
break;
case ProgramSchedulePlayoutType.Yaml:
<span>YAML</span>
case PlayoutScheduleKind.Sequential:
<span>Sequential</span>
break;
case ProgramSchedulePlayoutType.ExternalJson:
<span>External Json</span>
case PlayoutScheduleKind.ExternalJson:
<span>JSON (dizqueTV)</span>
break;
default:
<span></span>
@ -111,7 +104,7 @@ @@ -111,7 +104,7 @@
<MudProgressCircular Color="Color.Primary" Size="Size.Small" Indeterminate="true"/>
}
</div>
@if (context.PlayoutType == ProgramSchedulePlayoutType.Classic)
@if (context.ScheduleKind == PlayoutScheduleKind.Classic)
{
if (context.PlayoutMode is ChannelPlayoutMode.OnDemand)
{
@ -144,7 +137,7 @@ @@ -144,7 +137,7 @@
</MudIconButton>
</MudTooltip>
}
else if (context.PlayoutType == ProgramSchedulePlayoutType.ExternalJson)
else if (context.ScheduleKind == PlayoutScheduleKind.ExternalJson)
{
<MudTooltip Text="Edit External Json File">
<MudIconButton Icon="@Icons.Material.Filled.Edit"
@ -155,7 +148,7 @@ @@ -155,7 +148,7 @@
<div style="width: 48px"></div>
<div style="width: 48px"></div>
}
else if (context.PlayoutType == ProgramSchedulePlayoutType.Yaml)
else if (context.ScheduleKind == PlayoutScheduleKind.Sequential)
{
<MudTooltip Text="Edit Playout">
<MudIconButton Icon="@Icons.Material.Filled.Edit"
@ -171,7 +164,7 @@ @@ -171,7 +164,7 @@
</MudTooltip>
<div style="width: 48px"></div>
}
else if (context.PlayoutType == ProgramSchedulePlayoutType.Block)
else if (context.ScheduleKind == PlayoutScheduleKind.Block)
{
<MudTooltip Text="Edit Playout">
<MudIconButton Icon="@Icons.Material.Filled.Edit"
@ -297,8 +290,8 @@ @@ -297,8 +290,8 @@
private async Task PlayoutSelected(PlayoutNameViewModel playout)
{
// only show details for flood, block and YAML playouts
_selectedPlayoutId = playout.PlayoutType is ProgramSchedulePlayoutType.Classic or ProgramSchedulePlayoutType.Block or ProgramSchedulePlayoutType.Yaml
// only show details for flood, block and sequential playouts
_selectedPlayoutId = playout.ScheduleKind is PlayoutScheduleKind.Classic or PlayoutScheduleKind.Block or PlayoutScheduleKind.Sequential
? playout.PlayoutId
: null;

16
ErsatzTV/Pages/YamlPlayoutEditor.razor → ErsatzTV/Pages/SequentialPlayoutEditor.razor

@ -7,21 +7,21 @@ @@ -7,21 +7,21 @@
@inject ISnackbar Snackbar
@inject IMediator Mediator
@inject IEntityLocker EntityLocker;
@inject ILogger<YamlPlayoutEditor> Logger
@inject ILogger<SequentialPlayoutEditor> Logger
<MudForm Style="max-height: 100%">
<MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100; align-items: center">
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="ml-6" OnClick="@(_ => SaveChanges())" StartIcon="@Icons.Material.Filled.Save">
Save YAML File
Save Sequential Schedule
</MudButton>
</MudPaper>
<div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudText Typo="Typo.h5" Class="mb-2">@_channelName - YAML Playout</MudText>
<MudText Typo="Typo.h5" Class="mb-2">@_channelName - Sequential Schedule</MudText>
<MudDivider Class="mb-6"/>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>YAML File</MudText>
<MudText>Sequential Schedule</MudText>
</div>
<MudTextField @bind-Value="@_playout.TemplateFile" For="@(() => _playout.TemplateFile)"/>
</MudStack>
@ -92,14 +92,14 @@ @@ -92,14 +92,14 @@
}
Either<BaseError, PlayoutNameViewModel> result =
await Mediator.Send(new UpdateYamlPlayout(_playout.PlayoutId, _playout.TemplateFile), _cts.Token);
await Mediator.Send(new UpdateSequentialPlayout(_playout.PlayoutId, _playout.TemplateFile), _cts.Token);
result.Match(
_ => { Snackbar.Add($"Saved YAML file for playout {_channelName}", Severity.Success); },
_ => { Snackbar.Add($"Saved sequential schedule for playout {_channelName}", Severity.Success); },
error =>
{
Snackbar.Add($"Unexpected error saving YAML file: {error.Value}", Severity.Error);
Logger.LogError("Unexpected error saving YAML file: {Error}", error.Value);
Snackbar.Add($"Unexpected error saving sequential schedule: {error.Value}", Severity.Error);
Logger.LogError("Unexpected error saving sequential schedule: {Error}", error.Value);
});
}

6
ErsatzTV/Pages/YamlValidator.razor

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
@page "/system/troubleshooting/yaml"
@using ErsatzTV.Core.Interfaces.Scheduling
@implements IDisposable
@inject IYamlScheduleValidator YamlScheduleValidator
@inject ISequentialScheduleValidator SequentialScheduleValidator
<MudForm Style="max-height: 100%">
<div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
@ -99,8 +99,8 @@ @@ -99,8 +99,8 @@
_yamlText = await File.ReadAllTextAsync(_yamlFile);
try
{
_jsonText = YamlScheduleValidator.ToJson(_yamlText);
IList<string> messages = await YamlScheduleValidator.GetValidationMessages(_yamlText, _isImport);
_jsonText = SequentialScheduleValidator.ToJson(_yamlText);
IList<string> messages = await SequentialScheduleValidator.GetValidationMessages(_yamlText, _isImport);
_messagesCount = messages.Count;
_messages = string.Join("\n", messages);

2
ErsatzTV/PlayoutKind.cs

@ -3,6 +3,6 @@ namespace ErsatzTV; @@ -3,6 +3,6 @@ namespace ErsatzTV;
public static class PlayoutKind
{
public const string ExternalJson = "externaljson";
public const string Yaml = "yaml";
public const string Sequential = "sequential";
public const string Block = "block";
}

6
ErsatzTV/Resources/yaml-playout-import.schema.json → ErsatzTV/Resources/sequential-schedule-import.schema.json

@ -1,8 +1,8 @@ @@ -1,8 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://ersatztv.org/yaml-playout.schema.json",
"title": "YAML Playout Import",
"description": "An ErsatzTV YAML playout import definition",
"$id": "https://ersatztv.org/sequential-schedule.schema.json",
"title": "Sequential Schedule Import",
"description": "An ErsatzTV sequential schedule import definition",
"type": "object",
"properties": {
"content": {

6
ErsatzTV/Resources/yaml-playout.schema.json → ErsatzTV/Resources/sequential-schedule.schema.json

@ -1,8 +1,8 @@ @@ -1,8 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://ersatztv.org/yaml-playout.schema.json",
"title": "YAML Playout",
"description": "An ErsatzTV YAML playout definition",
"$id": "https://ersatztv.org/sequential-schedule.schema.json",
"title": "Sequential Schedule",
"description": "An ErsatzTV sequential schedule definition",
"type": "object",
"properties": {
"import": {

4
ErsatzTV/Services/RunOnce/ResourceExtractorService.cs

@ -24,8 +24,8 @@ public class ResourceExtractorService : BackgroundService @@ -24,8 +24,8 @@ public class ResourceExtractorService : BackgroundService
await ExtractResource(assembly, "song_progress_overlay.png", stoppingToken);
await ExtractResource(assembly, "song_progress_overlay_43.png", stoppingToken);
await ExtractResource(assembly, "ErsatzTV.png", stoppingToken);
await ExtractResource(assembly, "yaml-playout.schema.json", stoppingToken);
await ExtractResource(assembly, "yaml-playout-import.schema.json", stoppingToken);
await ExtractResource(assembly, "sequential-schedule.schema.json", stoppingToken);
await ExtractResource(assembly, "sequential-schedule-import.schema.json", stoppingToken);
await ExtractFontResource(assembly, "Sen.ttf", stoppingToken);
await ExtractFontResource(assembly, "Roboto-Regular.ttf", stoppingToken);

2
ErsatzTV/Startup.cs

@ -745,7 +745,7 @@ public class Startup @@ -745,7 +745,7 @@ public class Startup
services.AddScoped<IJellyfinSecretStore, JellyfinSecretStore>();
services.AddScoped<IEmbySecretStore, EmbySecretStore>();
services.AddScoped<IScriptEngine, ScriptEngine>();
services.AddScoped<IYamlScheduleValidator, YamlScheduleValidator>();
services.AddScoped<ISequentialScheduleValidator, SequentialScheduleValidator>();
services.AddScoped<PlexEtag>();

6
ErsatzTV/ViewModels/PlayoutEditViewModel.cs

@ -10,14 +10,14 @@ public class PlayoutEditViewModel @@ -10,14 +10,14 @@ public class PlayoutEditViewModel
public ChannelViewModel Channel { get; set; }
public ProgramScheduleViewModel ProgramSchedule { get; set; }
public string ExternalJsonFile { get; set; }
public string YamlFile { get; set; }
public string SequentialSchedule { get; set; }
public CreatePlayout ToCreate() =>
Kind switch
{
PlayoutKind.ExternalJson => new CreateExternalJsonPlayout(Channel.Id, ExternalJsonFile),
PlayoutKind.Yaml => new CreateYamlPlayout(Channel.Id, YamlFile),
PlayoutKind.Sequential => new CreateSequentialPlayout(Channel.Id, SequentialSchedule),
PlayoutKind.Block => new CreateBlockPlayout(Channel.Id),
_ => new CreateFloodPlayout(Channel.Id, ProgramSchedule.Id)
_ => new CreateClassicPlayout(Channel.Id, ProgramSchedule.Id)
};
}

Loading…
Cancel
Save