add alternate schedule system (#1105)
* start to add program schedule alternates * edit days of the week * editor improvements * save changes * build playouts using alternate schedules * reset playout as needed * add priority message
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Channels;
|
||||
|
||||
public record GetChannelNameByPlayoutId(int PlayoutId) : IRequest<Option<string>>;
|
||||
@@ -0,0 +1,24 @@
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
using ErsatzTV.Infrastructure.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ErsatzTV.Application.Channels;
|
||||
|
||||
public class GetChannelNameByPlayoutIdHandler : IRequestHandler<GetChannelNameByPlayoutId, Option<string>>
|
||||
{
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
|
||||
public GetChannelNameByPlayoutIdHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory;
|
||||
}
|
||||
|
||||
public async Task<Option<string>> Handle(GetChannelNameByPlayoutId request, CancellationToken cancellationToken)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
return await dbContext.Playouts
|
||||
.Include(p => p.Channel)
|
||||
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId)
|
||||
.MapT(p => p.Channel.Name);
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,36 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
|
||||
dbContext.Playouts
|
||||
.Include(p => p.Channel)
|
||||
.Include(p => p.Items)
|
||||
|
||||
.Include(p => p.ProgramScheduleAlternates)
|
||||
.ThenInclude(a => a.ProgramSchedule)
|
||||
.ThenInclude(ps => ps.Items)
|
||||
.ThenInclude(psi => psi.Collection)
|
||||
.Include(p => p.ProgramScheduleAlternates)
|
||||
.ThenInclude(a => a.ProgramSchedule)
|
||||
.ThenInclude(ps => ps.Items)
|
||||
.ThenInclude(psi => psi.MediaItem)
|
||||
.Include(p => p.ProgramScheduleAlternates)
|
||||
.ThenInclude(a => a.ProgramSchedule)
|
||||
.ThenInclude(ps => ps.Items)
|
||||
.ThenInclude(psi => psi.PreRollFiller)
|
||||
.Include(p => p.ProgramScheduleAlternates)
|
||||
.ThenInclude(a => a.ProgramSchedule)
|
||||
.ThenInclude(ps => ps.Items)
|
||||
.ThenInclude(psi => psi.MidRollFiller)
|
||||
.Include(p => p.ProgramScheduleAlternates)
|
||||
.ThenInclude(a => a.ProgramSchedule)
|
||||
.ThenInclude(ps => ps.Items)
|
||||
.ThenInclude(psi => psi.PostRollFiller)
|
||||
.Include(p => p.ProgramScheduleAlternates)
|
||||
.ThenInclude(a => a.ProgramSchedule)
|
||||
.ThenInclude(ps => ps.Items)
|
||||
.ThenInclude(psi => psi.TailFiller)
|
||||
.Include(p => p.ProgramScheduleAlternates)
|
||||
.ThenInclude(a => a.ProgramSchedule)
|
||||
.ThenInclude(ps => ps.Items)
|
||||
.ThenInclude(psi => psi.FallbackFiller)
|
||||
|
||||
.Include(p => p.ProgramScheduleAnchors)
|
||||
.ThenInclude(a => a.MediaItem)
|
||||
.Include(p => p.ProgramSchedule)
|
||||
@@ -98,6 +128,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
|
||||
.Include(p => p.ProgramSchedule)
|
||||
.ThenInclude(ps => ps.Items)
|
||||
.ThenInclude(psi => psi.FallbackFiller)
|
||||
|
||||
.SelectOneAsync(p => p.Id, p => p.Id == buildPlayout.PlayoutId)
|
||||
.Map(o => o.ToValidation<BaseError>("Playout does not exist."));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace ErsatzTV.Application.Playouts;
|
||||
|
||||
public record ReplacePlayoutAlternateSchedule(
|
||||
int Id,
|
||||
int Index,
|
||||
int ProgramScheduleId,
|
||||
List<DayOfWeek> DaysOfWeek,
|
||||
List<int> DaysOfMonth,
|
||||
List<int> MonthsOfYear);
|
||||
@@ -0,0 +1,6 @@
|
||||
using ErsatzTV.Core;
|
||||
|
||||
namespace ErsatzTV.Application.Playouts;
|
||||
|
||||
public record ReplacePlayoutAlternateScheduleItems
|
||||
(int PlayoutId, List<ReplacePlayoutAlternateSchedule> Items) : IRequest<Either<BaseError, Unit>>;
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
using System.Threading.Channels;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Scheduling;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
using ErsatzTV.Infrastructure.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ErsatzTV.Application.Playouts;
|
||||
|
||||
public class ReplacePlayoutAlternateScheduleItemsHandler :
|
||||
IRequestHandler<ReplacePlayoutAlternateScheduleItems, Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||
private readonly ILogger<ReplacePlayoutAlternateScheduleItemsHandler> _logger;
|
||||
|
||||
public ReplacePlayoutAlternateScheduleItemsHandler(
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
ChannelWriter<IBackgroundServiceRequest> channel,
|
||||
ILogger<ReplacePlayoutAlternateScheduleItemsHandler> logger)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_channel = channel;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<Either<BaseError, Unit>> Handle(
|
||||
ReplacePlayoutAlternateScheduleItems request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: validate that items is not empty
|
||||
|
||||
try
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
Option<Playout> maybePlayout = await dbContext.Playouts
|
||||
.Include(p => p.ProgramSchedule)
|
||||
.Include(p => p.ProgramScheduleAlternates)
|
||||
.ThenInclude(p => p.ProgramSchedule)
|
||||
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId);
|
||||
|
||||
foreach (Playout playout in maybePlayout)
|
||||
{
|
||||
ProgramSchedule existingDefault = playout.ProgramSchedule;
|
||||
|
||||
// exclude highest index
|
||||
int maxIndex = request.Items.Map(x => x.Index).Max();
|
||||
ReplacePlayoutAlternateSchedule highest = request.Items.First(x => x.Index == maxIndex);
|
||||
|
||||
ProgramScheduleAlternate[] existing = playout.ProgramScheduleAlternates.ToArray();
|
||||
|
||||
var incoming = request.Items.Except(new[] { highest }).ToList();
|
||||
|
||||
var toAdd = incoming.Filter(x => existing.All(e => e.Id != x.Id)).ToList();
|
||||
var toRemove = existing.Filter(e => incoming.All(m => m.Id != e.Id)).ToList();
|
||||
var toUpdate = incoming.Except(toAdd).ToList();
|
||||
|
||||
playout.ProgramScheduleAlternates.RemoveAll(toRemove.Contains);
|
||||
|
||||
foreach (ReplacePlayoutAlternateSchedule add in toAdd)
|
||||
{
|
||||
playout.ProgramScheduleAlternates.Add(
|
||||
new ProgramScheduleAlternate
|
||||
{
|
||||
PlayoutId = playout.Id,
|
||||
Index = add.Index,
|
||||
ProgramScheduleId = add.ProgramScheduleId,
|
||||
DaysOfWeek = add.DaysOfWeek,
|
||||
DaysOfMonth = add.DaysOfMonth,
|
||||
MonthsOfYear = add.MonthsOfYear
|
||||
});
|
||||
}
|
||||
|
||||
foreach (ReplacePlayoutAlternateSchedule update in toUpdate)
|
||||
{
|
||||
foreach (ProgramScheduleAlternate ex in existing.Filter(x => x.Id == update.Id))
|
||||
{
|
||||
ex.Index = update.Index;
|
||||
ex.ProgramScheduleId = update.ProgramScheduleId;
|
||||
ex.DaysOfWeek = update.DaysOfWeek;
|
||||
ex.DaysOfMonth = update.DaysOfMonth;
|
||||
ex.MonthsOfYear = update.MonthsOfYear;
|
||||
}
|
||||
}
|
||||
|
||||
// save highest index directly to playout
|
||||
if (playout.ProgramScheduleId != highest.ProgramScheduleId)
|
||||
{
|
||||
playout.ProgramScheduleId = highest.ProgramScheduleId;
|
||||
}
|
||||
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
Option<PlayoutItem> maybePlayoutItem = await dbContext.PlayoutItems
|
||||
.Filter(pi => pi.PlayoutId == request.PlayoutId)
|
||||
.OrderByDescending(pi => pi.Start)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.Map(Optional);
|
||||
|
||||
foreach (PlayoutItem playoutItem in maybePlayoutItem)
|
||||
{
|
||||
DateTimeOffset start = DateTimeOffset.Now;
|
||||
var daysToCheck = Enumerable.Range(0, (playoutItem.StartOffset - start).Days + 1)
|
||||
.Select(d => start.AddDays(d))
|
||||
.ToList();
|
||||
|
||||
foreach (DateTimeOffset dayToCheck in daysToCheck)
|
||||
{
|
||||
ProgramSchedule oldSchedule = PlayoutScheduleSelector.GetProgramScheduleFor(
|
||||
existingDefault,
|
||||
existing,
|
||||
dayToCheck);
|
||||
|
||||
ProgramSchedule newSchedule = PlayoutScheduleSelector.GetProgramScheduleFor(
|
||||
playout.ProgramSchedule,
|
||||
playout.ProgramScheduleAlternates,
|
||||
dayToCheck);
|
||||
|
||||
if (oldSchedule.Id != newSchedule.Id)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Alternate schedule change detected for day {Day}, schedule {One} => {Two}; will refresh playout",
|
||||
dayToCheck,
|
||||
oldSchedule.Name,
|
||||
newSchedule.Name);
|
||||
|
||||
await _channel.WriteAsync(
|
||||
new BuildPlayout(request.PlayoutId, PlayoutBuildMode.Refresh),
|
||||
cancellationToken);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Unit.Default;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BaseError.New(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,16 @@ internal static class Mapper
|
||||
playoutItem.StartOffset,
|
||||
GetDisplayDuration(playoutItem.FinishOffset - playoutItem.StartOffset));
|
||||
|
||||
internal static PlayoutAlternateScheduleViewModel ProjectToViewModel(
|
||||
ProgramScheduleAlternate programScheduleAlternate) =>
|
||||
new(
|
||||
programScheduleAlternate.Id,
|
||||
programScheduleAlternate.Index,
|
||||
programScheduleAlternate.ProgramScheduleId,
|
||||
programScheduleAlternate.DaysOfWeek,
|
||||
programScheduleAlternate.DaysOfMonth,
|
||||
programScheduleAlternate.MonthsOfYear);
|
||||
|
||||
private static string GetDisplayTitle(PlayoutItem playoutItem)
|
||||
{
|
||||
switch (playoutItem.MediaItem)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace ErsatzTV.Application.Playouts;
|
||||
|
||||
public record PlayoutAlternateScheduleViewModel(
|
||||
int Id,
|
||||
int Index,
|
||||
int ProgramScheduleId,
|
||||
ICollection<DayOfWeek> DaysOfWeek,
|
||||
ICollection<int> DaysOfMonth,
|
||||
ICollection<int> MonthsOfYear);
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Playouts;
|
||||
|
||||
public record GetPlayoutAlternateSchedules(int PlayoutId) : IRequest<List<PlayoutAlternateScheduleViewModel>>;
|
||||
@@ -0,0 +1,53 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
using ErsatzTV.Infrastructure.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using static ErsatzTV.Application.Playouts.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.Playouts;
|
||||
|
||||
public class GetPlayoutAlternateSchedulesHandler :
|
||||
IRequestHandler<GetPlayoutAlternateSchedules, List<PlayoutAlternateScheduleViewModel>>
|
||||
{
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
|
||||
public GetPlayoutAlternateSchedulesHandler(IDbContextFactory<TvContext> dbContextFactory) =>
|
||||
_dbContextFactory = dbContextFactory;
|
||||
|
||||
public async Task<List<PlayoutAlternateScheduleViewModel>> Handle(
|
||||
GetPlayoutAlternateSchedules request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
List<PlayoutAlternateScheduleViewModel> result = await dbContext.ProgramScheduleAlternates
|
||||
.Filter(psa => psa.PlayoutId == request.PlayoutId)
|
||||
.Include(psa => psa.ProgramSchedule)
|
||||
.Map(psa => ProjectToViewModel(psa))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
Option<ProgramSchedule> maybeDefaultSchedule = await dbContext.Playouts
|
||||
.Include(p => p.ProgramSchedule)
|
||||
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId)
|
||||
.MapT(p => p.ProgramSchedule);
|
||||
|
||||
foreach (ProgramSchedule defaultSchedule in maybeDefaultSchedule)
|
||||
{
|
||||
var psa = new ProgramScheduleAlternate
|
||||
{
|
||||
Id = -1,
|
||||
PlayoutId = request.PlayoutId,
|
||||
ProgramScheduleId = defaultSchedule.Id,
|
||||
ProgramSchedule = defaultSchedule,
|
||||
Index = result.Map(i => i.Index).DefaultIfEmpty().Max() + 1,
|
||||
DaysOfMonth = ProgramScheduleAlternate.AllDaysOfMonth(),
|
||||
DaysOfWeek = ProgramScheduleAlternate.AllDaysOfWeek(),
|
||||
MonthsOfYear = ProgramScheduleAlternate.AllMonthsOfYear()
|
||||
};
|
||||
|
||||
result.Add(ProjectToViewModel(psa));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -543,7 +543,8 @@ public class PlayoutBuilderTests
|
||||
},
|
||||
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" },
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
Items = new List<PlayoutItem>()
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
var configRepo = new Mock<IConfigElementRepository>();
|
||||
@@ -639,7 +640,8 @@ public class PlayoutBuilderTests
|
||||
},
|
||||
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" },
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
Items = new List<PlayoutItem>()
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
var configRepo = new Mock<IConfigElementRepository>();
|
||||
@@ -783,7 +785,8 @@ public class PlayoutBuilderTests
|
||||
},
|
||||
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" },
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
Items = new List<PlayoutItem>()
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
var configRepo = new Mock<IConfigElementRepository>();
|
||||
@@ -885,7 +888,8 @@ public class PlayoutBuilderTests
|
||||
},
|
||||
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" },
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
Items = new List<PlayoutItem>()
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
var configRepo = new Mock<IConfigElementRepository>();
|
||||
@@ -996,7 +1000,8 @@ public class PlayoutBuilderTests
|
||||
InFlood = true
|
||||
},
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
Items = new List<PlayoutItem>()
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
var configRepo = new Mock<IConfigElementRepository>();
|
||||
@@ -1100,7 +1105,8 @@ public class PlayoutBuilderTests
|
||||
},
|
||||
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" },
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
Items = new List<PlayoutItem>()
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
var configRepo = new Mock<IConfigElementRepository>();
|
||||
@@ -1208,7 +1214,8 @@ public class PlayoutBuilderTests
|
||||
},
|
||||
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" },
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
Items = new List<PlayoutItem>()
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
var configRepo = new Mock<IConfigElementRepository>();
|
||||
@@ -1321,7 +1328,8 @@ public class PlayoutBuilderTests
|
||||
MultipleRemaining = 2
|
||||
},
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
Items = new List<PlayoutItem>()
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
var configRepo = new Mock<IConfigElementRepository>();
|
||||
@@ -1423,7 +1431,8 @@ public class PlayoutBuilderTests
|
||||
},
|
||||
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" },
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
Items = new List<PlayoutItem>()
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
var configRepo = new Mock<IConfigElementRepository>();
|
||||
@@ -1536,7 +1545,8 @@ public class PlayoutBuilderTests
|
||||
DurationFinish = HoursAfterMidnight(3).UtcDateTime
|
||||
},
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
Items = new List<PlayoutItem>()
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
var configRepo = new Mock<IConfigElementRepository>();
|
||||
@@ -1660,7 +1670,8 @@ public class PlayoutBuilderTests
|
||||
},
|
||||
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" },
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
Items = new List<PlayoutItem>()
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
var configRepo = new Mock<IConfigElementRepository>();
|
||||
@@ -1776,7 +1787,8 @@ public class PlayoutBuilderTests
|
||||
},
|
||||
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" },
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
Items = new List<PlayoutItem>()
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
var configRepo = new Mock<IConfigElementRepository>();
|
||||
@@ -1852,7 +1864,8 @@ public class PlayoutBuilderTests
|
||||
},
|
||||
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" },
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
Items = new List<PlayoutItem>()
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
var configRepo = new Mock<IConfigElementRepository>();
|
||||
@@ -2049,7 +2062,8 @@ public class PlayoutBuilderTests
|
||||
},
|
||||
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
Items = new List<PlayoutItem>()
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
playout.ProgramScheduleAnchors.Add(
|
||||
@@ -2437,7 +2451,8 @@ public class PlayoutBuilderTests
|
||||
InFlood = true
|
||||
},
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
Items = new List<PlayoutItem>()
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
var configRepo = new Mock<IConfigElementRepository>();
|
||||
@@ -2548,7 +2563,8 @@ public class PlayoutBuilderTests
|
||||
MultipleRemaining = 2
|
||||
},
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
Items = new List<PlayoutItem>()
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
var configRepo = new Mock<IConfigElementRepository>();
|
||||
@@ -2659,7 +2675,8 @@ public class PlayoutBuilderTests
|
||||
DurationFinish = HoursAfterMidnight(3).UtcDateTime
|
||||
},
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
Items = new List<PlayoutItem>()
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
var configRepo = new Mock<IConfigElementRepository>();
|
||||
@@ -2768,7 +2785,8 @@ public class PlayoutBuilderTests
|
||||
ProgramSchedule = new ProgramSchedule { Items = items },
|
||||
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" },
|
||||
Items = new List<PlayoutItem>(),
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>()
|
||||
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(),
|
||||
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>()
|
||||
};
|
||||
|
||||
return new TestData(builder, playout);
|
||||
|
||||
@@ -7,6 +7,7 @@ public class Playout
|
||||
public Channel Channel { get; set; }
|
||||
public int ProgramScheduleId { get; set; }
|
||||
public ProgramSchedule ProgramSchedule { get; set; }
|
||||
public List<ProgramScheduleAlternate> ProgramScheduleAlternates { get; set; }
|
||||
public ProgramSchedulePlayoutType ProgramSchedulePlayoutType { get; set; }
|
||||
public List<PlayoutItem> Items { get; set; }
|
||||
public PlayoutAnchor Anchor { get; set; }
|
||||
|
||||
@@ -10,4 +10,5 @@ public class ProgramSchedule
|
||||
public bool RandomStartPoint { get; set; }
|
||||
public List<ProgramScheduleItem> Items { get; set; }
|
||||
public List<Playout> Playouts { get; set; }
|
||||
public List<ProgramScheduleAlternate> ProgramScheduleAlternates { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace ErsatzTV.Core.Domain;
|
||||
|
||||
public class ProgramScheduleAlternate
|
||||
{
|
||||
public static List<DayOfWeek> AllDaysOfWeek() => new()
|
||||
{
|
||||
DayOfWeek.Monday,
|
||||
DayOfWeek.Tuesday,
|
||||
DayOfWeek.Wednesday,
|
||||
DayOfWeek.Thursday,
|
||||
DayOfWeek.Friday,
|
||||
DayOfWeek.Saturday,
|
||||
DayOfWeek.Sunday
|
||||
};
|
||||
|
||||
public static List<int> AllDaysOfMonth() => Enumerable.Range(1, 31).ToList();
|
||||
public static List<int> AllMonthsOfYear() => Enumerable.Range(1, 12).ToList();
|
||||
|
||||
public int Id { get; set; }
|
||||
public int PlayoutId { get; set; }
|
||||
public Playout Playout { get; set; }
|
||||
public int ProgramScheduleId { get; set; }
|
||||
public ProgramSchedule ProgramSchedule { get; set; }
|
||||
public int Index { get; set; }
|
||||
public ICollection<DayOfWeek> DaysOfWeek { get; set; }
|
||||
public ICollection<int> DaysOfMonth { get; set; }
|
||||
public ICollection<int> MonthsOfYear { get; set; }
|
||||
}
|
||||
@@ -203,7 +203,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
parameters.Start,
|
||||
parameters.Finish,
|
||||
parameters.CollectionMediaItems,
|
||||
playout.ProgramSchedule.RandomStartPoint);
|
||||
true);
|
||||
|
||||
return playout;
|
||||
}
|
||||
@@ -237,11 +237,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
Map<CollectionKey, List<MediaItem>> collectionMediaItems = await GetCollectionMediaItems(playout);
|
||||
if (!collectionMediaItems.Any())
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Playout {Playout} schedule {Schedule} has no items",
|
||||
playout.Channel.Name,
|
||||
playout.ProgramSchedule.Name);
|
||||
|
||||
_logger.LogWarning("Playout {Playout} has no items", playout.Channel.Name);
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -365,13 +361,21 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
bool saveAnchorDate,
|
||||
bool randomStartPoint)
|
||||
{
|
||||
var sortedScheduleItems = playout.ProgramSchedule.Items.OrderBy(i => i.Index).ToList();
|
||||
ProgramSchedule activeSchedule = PlayoutScheduleSelector.GetProgramScheduleFor(
|
||||
playout.ProgramSchedule,
|
||||
playout.ProgramScheduleAlternates,
|
||||
playoutStart);
|
||||
|
||||
// random start points are disabled in some scenarios, so ensure it's enabled and active
|
||||
randomStartPoint = randomStartPoint && activeSchedule.RandomStartPoint;
|
||||
|
||||
var sortedScheduleItems = activeSchedule.Items.OrderBy(i => i.Index).ToList();
|
||||
CollectionEnumeratorState scheduleItemsEnumeratorState =
|
||||
playout.Anchor?.ScheduleItemsEnumeratorState ?? new CollectionEnumeratorState
|
||||
{ Seed = Random.Next(), Index = 0 };
|
||||
IScheduleItemsEnumerator scheduleItemsEnumerator = playout.ProgramSchedule.ShuffleScheduleItems
|
||||
? new ShuffledScheduleItemsEnumerator(playout.ProgramSchedule.Items, scheduleItemsEnumeratorState)
|
||||
: new OrderedScheduleItemsEnumerator(playout.ProgramSchedule.Items, scheduleItemsEnumeratorState);
|
||||
IScheduleItemsEnumerator scheduleItemsEnumerator = activeSchedule.ShuffleScheduleItems
|
||||
? new ShuffledScheduleItemsEnumerator(activeSchedule.Items, scheduleItemsEnumeratorState)
|
||||
: new OrderedScheduleItemsEnumerator(activeSchedule.Items, scheduleItemsEnumeratorState);
|
||||
var collectionEnumerators = new Dictionary<CollectionKey, IMediaCollectionEnumerator>();
|
||||
foreach ((CollectionKey collectionKey, List<MediaItem> mediaItems) in collectionMediaItems)
|
||||
{
|
||||
@@ -381,7 +385,13 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
PlaybackOrder playbackOrder = maybeScheduleItem
|
||||
.Match(item => item.PlaybackOrder, () => PlaybackOrder.Shuffle);
|
||||
IMediaCollectionEnumerator enumerator =
|
||||
await GetMediaCollectionEnumerator(playout, collectionKey, mediaItems, playbackOrder, randomStartPoint);
|
||||
await GetMediaCollectionEnumerator(
|
||||
playout,
|
||||
activeSchedule,
|
||||
collectionKey,
|
||||
mediaItems,
|
||||
playbackOrder,
|
||||
randomStartPoint);
|
||||
collectionEnumerators.Add(collectionKey, enumerator);
|
||||
}
|
||||
|
||||
@@ -533,7 +543,11 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
}
|
||||
|
||||
// build program schedule anchors
|
||||
playout.ProgramScheduleAnchors = BuildProgramScheduleAnchors(playout, collectionEnumerators, saveAnchorDate);
|
||||
playout.ProgramScheduleAnchors = BuildProgramScheduleAnchors(
|
||||
playout,
|
||||
activeSchedule,
|
||||
collectionEnumerators,
|
||||
saveAnchorDate);
|
||||
|
||||
return playout;
|
||||
}
|
||||
@@ -541,6 +555,8 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
private async Task<Map<CollectionKey, List<MediaItem>>> GetCollectionMediaItems(Playout playout)
|
||||
{
|
||||
var collectionKeys = playout.ProgramSchedule.Items
|
||||
.Append(playout.ProgramScheduleAlternates.Bind(psa => psa.ProgramSchedule.Items))
|
||||
.DistinctBy(i => i.Id)
|
||||
.SelectMany(CollectionKeysForItem)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
@@ -640,6 +656,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
|
||||
private static List<PlayoutProgramScheduleAnchor> BuildProgramScheduleAnchors(
|
||||
Playout playout,
|
||||
ProgramSchedule activeSchedule,
|
||||
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators,
|
||||
bool saveAnchorDate)
|
||||
{
|
||||
@@ -665,8 +682,8 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
{
|
||||
Playout = playout,
|
||||
PlayoutId = playout.Id,
|
||||
ProgramSchedule = playout.ProgramSchedule,
|
||||
ProgramScheduleId = playout.ProgramScheduleId,
|
||||
ProgramSchedule = activeSchedule,
|
||||
ProgramScheduleId = activeSchedule.Id,
|
||||
CollectionType = collectionKey.CollectionType,
|
||||
CollectionId = collectionKey.CollectionId,
|
||||
MultiCollectionId = collectionKey.MultiCollectionId,
|
||||
@@ -694,6 +711,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
|
||||
private async Task<IMediaCollectionEnumerator> GetMediaCollectionEnumerator(
|
||||
Playout playout,
|
||||
ProgramSchedule activeSchedule,
|
||||
CollectionKey collectionKey,
|
||||
List<MediaItem> mediaItems,
|
||||
PlaybackOrder playbackOrder,
|
||||
@@ -702,7 +720,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
Option<PlayoutProgramScheduleAnchor> maybeAnchor = playout.ProgramScheduleAnchors
|
||||
.OrderByDescending(a => a.AnchorDate ?? DateTime.MaxValue)
|
||||
.FirstOrDefault(
|
||||
a => a.ProgramScheduleId == playout.ProgramScheduleId
|
||||
a => a.ProgramScheduleId == activeSchedule.Id
|
||||
&& a.CollectionType == collectionKey.CollectionType
|
||||
&& a.CollectionId == collectionKey.CollectionId
|
||||
&& a.MultiCollectionId == collectionKey.MultiCollectionId
|
||||
@@ -757,7 +775,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
return new ShuffleInOrderCollectionEnumerator(
|
||||
await GetCollectionItemsForShuffleInOrder(collectionKey),
|
||||
state,
|
||||
playout.ProgramSchedule.RandomStartPoint);
|
||||
activeSchedule.RandomStartPoint);
|
||||
case PlaybackOrder.MultiEpisodeShuffle when
|
||||
collectionKey.CollectionType == ProgramScheduleItemCollectionType.TelevisionShow &&
|
||||
collectionKey.MediaItemId.HasValue:
|
||||
@@ -795,7 +813,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
case PlaybackOrder.MultiEpisodeShuffle:
|
||||
case PlaybackOrder.Shuffle:
|
||||
return new ShuffledMediaCollectionEnumerator(
|
||||
await GetGroupedMediaItemsForShuffle(playout, mediaItems, collectionKey),
|
||||
await GetGroupedMediaItemsForShuffle(activeSchedule, mediaItems, collectionKey),
|
||||
state);
|
||||
default:
|
||||
// TODO: handle this error case differently?
|
||||
@@ -804,7 +822,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
}
|
||||
|
||||
private async Task<List<GroupedMediaItem>> GetGroupedMediaItemsForShuffle(
|
||||
Playout playout,
|
||||
ProgramSchedule activeSchedule,
|
||||
List<MediaItem> mediaItems,
|
||||
CollectionKey collectionKey)
|
||||
{
|
||||
@@ -816,10 +834,8 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
return MultiCollectionGrouper.GroupMediaItems(collections);
|
||||
}
|
||||
|
||||
return playout.ProgramSchedule.KeepMultiPartEpisodesTogether
|
||||
? MultiPartEpisodeGrouper.GroupMediaItems(
|
||||
mediaItems,
|
||||
playout.ProgramSchedule.TreatCollectionsAsShows)
|
||||
return activeSchedule.KeepMultiPartEpisodesTogether
|
||||
? MultiPartEpisodeGrouper.GroupMediaItems(mediaItems, activeSchedule.TreatCollectionsAsShows)
|
||||
: mediaItems.Map(mi => new GroupedMediaItem(mi, null)).ToList();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Core.Scheduling;
|
||||
|
||||
public static class PlayoutScheduleSelector
|
||||
{
|
||||
public static ProgramSchedule GetProgramScheduleFor(
|
||||
ProgramSchedule defaultSchedule,
|
||||
IEnumerable<ProgramScheduleAlternate> alternates,
|
||||
DateTimeOffset date)
|
||||
{
|
||||
foreach (ProgramScheduleAlternate alternate in alternates.OrderBy(x => x.Index))
|
||||
{
|
||||
bool daysOfWeek = alternate.DaysOfWeek.Count is 0 or 7 ||
|
||||
alternate.DaysOfWeek.Contains(date.DayOfWeek);
|
||||
|
||||
if (!daysOfWeek)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool daysOfMonth = alternate.DaysOfMonth.Count is 0 or 31 ||
|
||||
alternate.DaysOfMonth.Contains(date.Day);
|
||||
if (!daysOfMonth)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool monthOfYear = alternate.MonthsOfYear.Count is 0 or 12 ||
|
||||
alternate.MonthsOfYear.Contains(date.Month);
|
||||
|
||||
if (monthOfYear)
|
||||
{
|
||||
return alternate.ProgramSchedule;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultSchedule;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations;
|
||||
|
||||
public class CollectionValueComparer<T> : ValueComparer<ICollection<T>>
|
||||
{
|
||||
public CollectionValueComparer() : base(
|
||||
(c1, c2) => c1.SequenceEqual(c2),
|
||||
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
|
||||
c => c.ToHashSet())
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations;
|
||||
|
||||
public class EnumCollectionJsonValueConverter<T> : ValueConverter<ICollection<T>, string> where T : Enum
|
||||
{
|
||||
public EnumCollectionJsonValueConverter() : base(
|
||||
v => JsonConvert.SerializeObject(v.Select(e => e.ToString()).ToList()),
|
||||
v => JsonConvert.DeserializeObject<ICollection<string>>(v)
|
||||
.Select(e => (T)Enum.Parse(typeof(T), e)).ToList())
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,11 @@ public class PlayoutConfiguration : IEntityTypeConfiguration<Playout>
|
||||
{
|
||||
builder.ToTable("Playout");
|
||||
|
||||
builder.HasMany(p => p.ProgramScheduleAlternates)
|
||||
.WithOne(a => a.Playout)
|
||||
.HasForeignKey(a => a.PlayoutId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasMany(p => p.Items)
|
||||
.WithOne(pi => pi.Playout)
|
||||
.HasForeignKey(pi => pi.PlayoutId)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations;
|
||||
|
||||
public class ProgramScheduleAlternateConfiguration : IEntityTypeConfiguration<ProgramScheduleAlternate>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ProgramScheduleAlternate> builder)
|
||||
{
|
||||
builder.ToTable("ProgramScheduleAlternate");
|
||||
|
||||
var intCollectionValueConverter = new ValueConverter<ICollection<int>, string>(
|
||||
i => string.Join(",", i),
|
||||
s => string.IsNullOrWhiteSpace(s)
|
||||
? Array.Empty<int>()
|
||||
: s.Split(new[] { ',' }).Select(int.Parse).ToArray());
|
||||
|
||||
var intCollectionValueComparer = new CollectionValueComparer<int>();
|
||||
|
||||
builder.Property(t => t.DaysOfMonth)
|
||||
.HasConversion(intCollectionValueConverter)
|
||||
.Metadata.SetValueComparer(intCollectionValueComparer);
|
||||
|
||||
builder.Property(t => t.MonthsOfYear)
|
||||
.HasConversion(intCollectionValueConverter)
|
||||
.Metadata.SetValueComparer(intCollectionValueComparer);
|
||||
|
||||
builder.Property(t => t.DaysOfWeek)
|
||||
.HasConversion(new EnumCollectionJsonValueConverter<DayOfWeek>())
|
||||
.Metadata.SetValueComparer(new CollectionValueComparer<DayOfWeek>());
|
||||
}
|
||||
}
|
||||
@@ -22,5 +22,10 @@ public class ProgramScheduleConfiguration : IEntityTypeConfiguration<ProgramSche
|
||||
.WithOne(p => p.ProgramSchedule)
|
||||
.HasForeignKey(p => p.ProgramScheduleId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasMany(p => p.ProgramScheduleAlternates)
|
||||
.WithOne(a => a.ProgramSchedule)
|
||||
.HasForeignKey(a => a.ProgramScheduleId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ public class TvContext : DbContext
|
||||
public DbSet<ProgramSchedule> ProgramSchedules { get; set; }
|
||||
public DbSet<ProgramScheduleItem> ProgramScheduleItems { get; set; }
|
||||
public DbSet<Playout> Playouts { get; set; }
|
||||
public DbSet<ProgramScheduleAlternate> ProgramScheduleAlternates { get; set; }
|
||||
public DbSet<PlayoutItem> PlayoutItems { get; set; }
|
||||
public DbSet<PlayoutProgramScheduleAnchor> PlayoutProgramScheduleItemAnchors { get; set; }
|
||||
public DbSet<FFmpegProfile> FFmpegProfiles { get; set; }
|
||||
|
||||
Generated
+4394
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddProgramScheduleAlternates : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ProgramScheduleAlternate",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
PlayoutId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
ProgramScheduleId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Index = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
DaysOfWeek = table.Column<string>(type: "TEXT", nullable: true),
|
||||
DaysOfMonth = table.Column<string>(type: "TEXT", nullable: true),
|
||||
MonthsOfYear = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ProgramScheduleAlternate", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ProgramScheduleAlternate_Playout_PlayoutId",
|
||||
column: x => x.PlayoutId,
|
||||
principalTable: "Playout",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_ProgramScheduleAlternate_ProgramSchedule_ProgramScheduleId",
|
||||
column: x => x.ProgramScheduleId,
|
||||
principalTable: "ProgramSchedule",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ProgramScheduleAlternate_PlayoutId",
|
||||
table: "ProgramScheduleAlternate",
|
||||
column: "PlayoutId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ProgramScheduleAlternate_ProgramScheduleId",
|
||||
table: "ProgramScheduleAlternate",
|
||||
column: "ProgramScheduleId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ProgramScheduleAlternate");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.10");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.1");
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
|
||||
{
|
||||
@@ -820,6 +820,8 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.HasIndex("MediaSourceId");
|
||||
|
||||
b.ToTable("Library", (string)null);
|
||||
|
||||
b.UseTptMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryFolder", b =>
|
||||
@@ -914,6 +916,8 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("MediaFile", (string)null);
|
||||
|
||||
b.UseTptMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b =>
|
||||
@@ -933,6 +937,8 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.HasIndex("LibraryPathId");
|
||||
|
||||
b.ToTable("MediaItem", (string)null);
|
||||
|
||||
b.UseTptMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b =>
|
||||
@@ -944,6 +950,8 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("MediaSource", (string)null);
|
||||
|
||||
b.UseTptMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MediaStream", b =>
|
||||
@@ -1598,6 +1606,39 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.ToTable("ProgramSchedule", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleAlternate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("DaysOfMonth")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DaysOfWeek")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Index")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("MonthsOfYear")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("PlayoutId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ProgramScheduleId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PlayoutId");
|
||||
|
||||
b.HasIndex("ProgramScheduleId");
|
||||
|
||||
b.ToTable("ProgramScheduleAlternate", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1692,6 +1733,8 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.HasIndex("WatermarkId");
|
||||
|
||||
b.ToTable("ProgramScheduleItem", (string)null);
|
||||
|
||||
b.UseTptMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Resolution", b =>
|
||||
@@ -2265,13 +2308,6 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.ToTable("JellyfinPathInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Artist", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaItem");
|
||||
|
||||
b.ToTable("Artist", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyLibrary", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Library");
|
||||
@@ -2285,31 +2321,6 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.ToTable("EmbyLibrary", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyMediaSource", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaSource");
|
||||
|
||||
b.Property<string>("OperatingSystem")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ServerName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.ToTable("EmbyMediaSource", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Episode", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaItem");
|
||||
|
||||
b.Property<int>("SeasonId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasIndex("SeasonId");
|
||||
|
||||
b.ToTable("Episode", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinLibrary", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Library");
|
||||
@@ -2323,19 +2334,6 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.ToTable("JellyfinLibrary", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinMediaSource", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaSource");
|
||||
|
||||
b.Property<string>("OperatingSystem")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ServerName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.ToTable("JellyfinMediaSource", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Library");
|
||||
@@ -2343,11 +2341,49 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.ToTable("LocalLibrary", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b =>
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaSource");
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Library");
|
||||
|
||||
b.ToTable("LocalMediaSource", (string)null);
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("ShouldSyncItems")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.ToTable("PlexLibrary", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaFile", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaFile");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("PlexId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.ToTable("PlexMediaFile", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Artist", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaItem");
|
||||
|
||||
b.ToTable("Artist", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Episode", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaItem");
|
||||
|
||||
b.Property<int>("SeasonId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasIndex("SeasonId");
|
||||
|
||||
b.ToTable("Episode", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b =>
|
||||
@@ -2376,30 +2412,66 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.ToTable("OtherVideo", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b =>
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Library");
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaItem");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("ShouldSyncItems")
|
||||
b.Property<int>("SeasonNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.ToTable("PlexLibrary", (string)null);
|
||||
b.Property<int>("ShowId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasIndex("ShowId");
|
||||
|
||||
b.ToTable("Season", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaFile", b =>
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaFile");
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaItem");
|
||||
|
||||
b.Property<string>("Key")
|
||||
b.ToTable("Show", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Song", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaItem");
|
||||
|
||||
b.ToTable("Song", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyMediaSource", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaSource");
|
||||
|
||||
b.Property<string>("OperatingSystem")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("PlexId")
|
||||
.HasColumnType("INTEGER");
|
||||
b.Property<string>("ServerName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.ToTable("PlexMediaFile", (string)null);
|
||||
b.ToTable("EmbyMediaSource", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinMediaSource", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaSource");
|
||||
|
||||
b.Property<string>("OperatingSystem")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ServerName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.ToTable("JellyfinMediaSource", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaSource");
|
||||
|
||||
b.ToTable("LocalMediaSource", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b =>
|
||||
@@ -2461,35 +2533,6 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.ToTable("ProgramScheduleOneItem", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaItem");
|
||||
|
||||
b.Property<int>("SeasonNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ShowId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasIndex("ShowId");
|
||||
|
||||
b.ToTable("Season", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaItem");
|
||||
|
||||
b.ToTable("Show", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Song", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.MediaItem");
|
||||
|
||||
b.ToTable("Song", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyEpisode", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Episode");
|
||||
@@ -2503,45 +2546,6 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.ToTable("EmbyEpisode", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyMovie", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Movie");
|
||||
|
||||
b.Property<string>("Etag")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.ToTable("EmbyMovie", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbySeason", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Season");
|
||||
|
||||
b.Property<string>("Etag")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.ToTable("EmbySeason", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyShow", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Show");
|
||||
|
||||
b.Property<string>("Etag")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.ToTable("EmbyShow", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinEpisode", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Episode");
|
||||
@@ -2555,45 +2559,6 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.ToTable("JellyfinEpisode", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinMovie", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Movie");
|
||||
|
||||
b.Property<string>("Etag")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.ToTable("JellyfinMovie", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinSeason", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Season");
|
||||
|
||||
b.Property<string>("Etag")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.ToTable("JellyfinSeason", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinShow", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Show");
|
||||
|
||||
b.Property<string>("Etag")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.ToTable("JellyfinShow", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexEpisode", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Episode");
|
||||
@@ -2607,6 +2572,32 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.ToTable("PlexEpisode", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyMovie", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Movie");
|
||||
|
||||
b.Property<string>("Etag")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.ToTable("EmbyMovie", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinMovie", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Movie");
|
||||
|
||||
b.Property<string>("Etag")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.ToTable("JellyfinMovie", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovie", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Movie");
|
||||
@@ -2620,6 +2611,32 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.ToTable("PlexMovie", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbySeason", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Season");
|
||||
|
||||
b.Property<string>("Etag")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.ToTable("EmbySeason", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinSeason", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Season");
|
||||
|
||||
b.Property<string>("Etag")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.ToTable("JellyfinSeason", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexSeason", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Season");
|
||||
@@ -2633,6 +2650,32 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.ToTable("PlexSeason", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyShow", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Show");
|
||||
|
||||
b.Property<string>("Etag")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.ToTable("EmbyShow", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinShow", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Show");
|
||||
|
||||
b.Property<string>("Etag")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.ToTable("JellyfinShow", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexShow", b =>
|
||||
{
|
||||
b.HasBaseType("ErsatzTV.Core.Domain.Show");
|
||||
@@ -3371,6 +3414,25 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.Navigation("PlexMediaSource");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleAlternate", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout")
|
||||
.WithMany("ProgramScheduleAlternates")
|
||||
.HasForeignKey("PlayoutId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule")
|
||||
.WithMany("ProgramScheduleAlternates")
|
||||
.HasForeignKey("ProgramScheduleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Playout");
|
||||
|
||||
b.Navigation("ProgramSchedule");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Collection", "Collection")
|
||||
@@ -3675,15 +3737,6 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Artist", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.MediaItem", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.Artist", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyLibrary", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Library", null)
|
||||
@@ -3693,11 +3746,47 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyMediaSource", b =>
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinLibrary", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.MediaSource", null)
|
||||
b.HasOne("ErsatzTV.Core.Domain.Library", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.EmbyMediaSource", "Id")
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.JellyfinLibrary", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Library", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.LocalLibrary", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Library", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.PlexLibrary", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaFile", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.MediaFile", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.PlexMediaFile", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Artist", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.MediaItem", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.Artist", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
@@ -3719,42 +3808,6 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.Navigation("Season");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinLibrary", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Library", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.JellyfinLibrary", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinMediaSource", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.MediaSource", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.JellyfinMediaSource", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Library", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.LocalLibrary", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.MediaSource", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.LocalMediaSource", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.MediaItem", null)
|
||||
@@ -3790,20 +3843,64 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b =>
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Library", null)
|
||||
b.HasOne("ErsatzTV.Core.Domain.MediaItem", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.PlexLibrary", "Id")
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.Season", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.Show", "Show")
|
||||
.WithMany("Seasons")
|
||||
.HasForeignKey("ShowId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Show");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.MediaItem", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.Show", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaFile", b =>
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Song", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.MediaFile", null)
|
||||
b.HasOne("ErsatzTV.Core.Domain.MediaItem", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.PlexMediaFile", "Id")
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.Song", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyMediaSource", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.MediaSource", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.EmbyMediaSource", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinMediaSource", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.MediaSource", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.JellyfinMediaSource", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.MediaSource", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.LocalMediaSource", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
@@ -3853,41 +3950,6 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.MediaItem", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.Season", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.Show", "Show")
|
||||
.WithMany("Seasons")
|
||||
.HasForeignKey("ShowId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Show");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.MediaItem", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.Show", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Song", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.MediaItem", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.Song", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyEpisode", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Episode", null)
|
||||
@@ -3897,33 +3959,6 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyMovie", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Movie", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.EmbyMovie", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbySeason", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Season", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.EmbySeason", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyShow", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Show", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.EmbyShow", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinEpisode", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Episode", null)
|
||||
@@ -3933,33 +3968,6 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinMovie", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Movie", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.JellyfinMovie", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinSeason", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Season", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.JellyfinSeason", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinShow", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Show", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.JellyfinShow", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexEpisode", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Episode", null)
|
||||
@@ -3969,6 +3977,24 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyMovie", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Movie", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.EmbyMovie", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinMovie", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Movie", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.JellyfinMovie", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovie", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Movie", null)
|
||||
@@ -3978,6 +4004,24 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbySeason", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Season", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.EmbySeason", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinSeason", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Season", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.JellyfinSeason", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexSeason", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Season", null)
|
||||
@@ -3987,6 +4031,24 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyShow", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Show", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.EmbyShow", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinShow", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Show", null)
|
||||
.WithOne()
|
||||
.HasForeignKey("ErsatzTV.Core.Domain.JellyfinShow", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexShow", b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Show", null)
|
||||
@@ -4157,6 +4219,8 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
{
|
||||
b.Navigation("Items");
|
||||
|
||||
b.Navigation("ProgramScheduleAlternates");
|
||||
|
||||
b.Navigation("ProgramScheduleAnchors");
|
||||
});
|
||||
|
||||
@@ -4165,6 +4229,8 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.Navigation("Items");
|
||||
|
||||
b.Navigation("Playouts");
|
||||
|
||||
b.Navigation("ProgramScheduleAlternates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.SeasonMetadata", b =>
|
||||
@@ -4233,42 +4299,28 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.Navigation("Guids");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Artist", b =>
|
||||
{
|
||||
b.Navigation("ArtistMetadata");
|
||||
|
||||
b.Navigation("MusicVideos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyLibrary", b =>
|
||||
{
|
||||
b.Navigation("PathInfos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyMediaSource", b =>
|
||||
{
|
||||
b.Navigation("Connections");
|
||||
|
||||
b.Navigation("PathReplacements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Episode", b =>
|
||||
{
|
||||
b.Navigation("EpisodeMetadata");
|
||||
|
||||
b.Navigation("MediaVersions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinLibrary", b =>
|
||||
{
|
||||
b.Navigation("PathInfos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinMediaSource", b =>
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Artist", b =>
|
||||
{
|
||||
b.Navigation("Connections");
|
||||
b.Navigation("ArtistMetadata");
|
||||
|
||||
b.Navigation("PathReplacements");
|
||||
b.Navigation("MusicVideos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Episode", b =>
|
||||
{
|
||||
b.Navigation("EpisodeMetadata");
|
||||
|
||||
b.Navigation("MediaVersions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b =>
|
||||
@@ -4292,13 +4344,6 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.Navigation("OtherVideoMetadata");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b =>
|
||||
{
|
||||
b.Navigation("Connections");
|
||||
|
||||
b.Navigation("PathReplacements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b =>
|
||||
{
|
||||
b.Navigation("Episodes");
|
||||
@@ -4319,6 +4364,27 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
|
||||
b.Navigation("SongMetadata");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyMediaSource", b =>
|
||||
{
|
||||
b.Navigation("Connections");
|
||||
|
||||
b.Navigation("PathReplacements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinMediaSource", b =>
|
||||
{
|
||||
b.Navigation("Connections");
|
||||
|
||||
b.Navigation("PathReplacements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b =>
|
||||
{
|
||||
b.Navigation("Connections");
|
||||
|
||||
b.Navigation("PathReplacements");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
@page "/playouts/{Id:int}/alternate-schedules"
|
||||
@using ErsatzTV.Application.ProgramSchedules
|
||||
@using ErsatzTV.Application.Playouts
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@using ErsatzTV.Application.Channels
|
||||
@using System.Text
|
||||
@using System.Globalization
|
||||
@implements IDisposable
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ILogger<ScheduleItemsEditor> Logger
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IMediator Mediator
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudTable Hover="true" Items="_items.OrderBy(i => i.Index)" Dense="true" @bind-SelectedItem="_selectedItem">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">@_channelName Alternate Schedules</MudText>
|
||||
<MudSpacer />
|
||||
<MudText Typo="Typo.subtitle1" Class="mr-3">In priority order from top to bottom</MudText>
|
||||
</ToolBarContent>
|
||||
<ColGroup>
|
||||
<col/>
|
||||
<col/>
|
||||
<col/>
|
||||
<col/>
|
||||
<col style="width: 60px;"/>
|
||||
<col style="width: 60px;"/>
|
||||
<col style="width: 60px;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>Schedule</MudTh>
|
||||
<MudTh>Days of the Week</MudTh>
|
||||
<MudTh>Days of the Month</MudTh>
|
||||
<MudTh>Months</MudTh>
|
||||
<MudTh/>
|
||||
<MudTh/>
|
||||
<MudTh/>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Schedule">
|
||||
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)">
|
||||
@context.ProgramSchedule.Name
|
||||
</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Days of the Week">
|
||||
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)">
|
||||
@ToDaysOfWeekString(context.DaysOfWeek)
|
||||
</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Days of the Month">
|
||||
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)">
|
||||
@ToDaysOfMonthString(context.DaysOfMonth)
|
||||
</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Months">
|
||||
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)">
|
||||
@ToMonthsOfYearString(context.MonthsOfYear)
|
||||
</MudText>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ArrowUpward"
|
||||
OnClick="@(_ => MoveItemUp(context))"
|
||||
Disabled="@(_items.All(x => x.Index >= context.Index))">
|
||||
</MudIconButton>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ArrowDownward"
|
||||
OnClick="@(_ => MoveItemDown(context))"
|
||||
Disabled="@(_items.All(x => x.Index <= context.Index))">
|
||||
</MudIconButton>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
OnClick="@(_ => RemoveAlternateSchedule(context))"
|
||||
Disabled="@(_items.Count == 1)">
|
||||
</MudIconButton>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Default" OnClick="@(_ => AddAlternateSchedule())" Class="mt-4">
|
||||
Add Alternate Schedule
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveChanges())" Class="mt-4 ml-4">
|
||||
Save Changes
|
||||
</MudButton>
|
||||
|
||||
@if (_selectedItem is not null)
|
||||
{
|
||||
<EditForm Model="_selectedItem">
|
||||
<FluentValidator/>
|
||||
<div style="display: flex; flex-direction: row;" class="mt-6">
|
||||
<div style="flex-grow: 1; max-width: 400px;" class="mr-6">
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudSelect Label="Schedule" @bind-Value="_selectedItem.ProgramSchedule" For="@(() => _selectedItem.ProgramSchedule)">
|
||||
@foreach (ProgramScheduleViewModel schedule in _schedules)
|
||||
{
|
||||
<MudSelectItem Value="@schedule">@schedule.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
<MudCard Class="mt-4">
|
||||
<MudCardContent>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudCheckBox T="bool" Label="@_dtf.GetDayName(DayOfWeek.Monday)"
|
||||
Checked="@(_selectedItem.DaysOfWeek.Contains(DayOfWeek.Monday))"
|
||||
CheckedChanged="@(c => DayOfWeekChanged(DayOfWeek.Monday, c))"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-2">
|
||||
<MudCheckBox T="bool" Label="@_dtf.GetDayName(DayOfWeek.Tuesday)"
|
||||
Checked="@(_selectedItem.DaysOfWeek.Contains(DayOfWeek.Tuesday))"
|
||||
CheckedChanged="@(c => DayOfWeekChanged(DayOfWeek.Tuesday, c))"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-2">
|
||||
<MudCheckBox T="bool" Label="@_dtf.GetDayName(DayOfWeek.Wednesday)"
|
||||
Checked="@(_selectedItem.DaysOfWeek.Contains(DayOfWeek.Wednesday))"
|
||||
CheckedChanged="@(c => DayOfWeekChanged(DayOfWeek.Wednesday, c))"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-2">
|
||||
<MudCheckBox T="bool" Label="@_dtf.GetDayName(DayOfWeek.Thursday)"
|
||||
Checked="@(_selectedItem.DaysOfWeek.Contains(DayOfWeek.Thursday))"
|
||||
CheckedChanged="@(c => DayOfWeekChanged(DayOfWeek.Thursday, c))"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-2">
|
||||
<MudCheckBox T="bool" Label="@_dtf.GetDayName(DayOfWeek.Friday)"
|
||||
Checked="@(_selectedItem.DaysOfWeek.Contains(DayOfWeek.Friday))"
|
||||
CheckedChanged="@(c => DayOfWeekChanged(DayOfWeek.Friday, c))"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-2">
|
||||
<MudCheckBox T="bool" Label="@_dtf.GetDayName(DayOfWeek.Saturday)"
|
||||
Checked="@(_selectedItem.DaysOfWeek.Contains(DayOfWeek.Saturday))"
|
||||
CheckedChanged="@(c => DayOfWeekChanged(DayOfWeek.Saturday, c))"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-2">
|
||||
<MudCheckBox T="bool" Label="@_dtf.GetDayName(DayOfWeek.Sunday)"
|
||||
Checked="@(_selectedItem.DaysOfWeek.Contains(DayOfWeek.Sunday))"
|
||||
CheckedChanged="@(c => DayOfWeekChanged(DayOfWeek.Sunday, c))"/>
|
||||
</MudElement>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(_ => SelectWeekdays())">
|
||||
Weekdays
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(_ => SelectWeekends())">
|
||||
Weekends
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(_ => SelectAllDaysOfWeek())">
|
||||
All
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(_ => SelectNoDaysOfWeek())">
|
||||
None
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</div>
|
||||
<div style="flex-grow: 1; max-width: 400px;" class="mr-6">
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudGrid Justify="Justify.FlexStart" Class="mt-3">
|
||||
@foreach (int day in Enumerable.Range(1, 31))
|
||||
{
|
||||
<MudItem xs="3">
|
||||
<MudCheckBox T="bool" Label="@day.ToString()"
|
||||
Checked="@(_selectedItem.DaysOfMonth.Contains(day))"
|
||||
CheckedChanged="@(c => DayOfMonthChanged(day, c))"/>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(_ => SelectAllDaysOfMonth())">
|
||||
All
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(_ => SelectNoDaysOfMonth())">
|
||||
None
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</div>
|
||||
<div style="flex-grow: 1; max-width: 400px;">
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudCheckBox T="bool" Label="@_dtf.GetMonthName(1)"
|
||||
Checked="@(_selectedItem.MonthsOfYear.Contains(1))"
|
||||
CheckedChanged="@(c => MonthOfYearChanged(1, c))"/>
|
||||
</MudElement>
|
||||
@foreach (int month in Enumerable.Range(2, 11))
|
||||
{
|
||||
<MudElement HtmlTag="div" Class="mt-2">
|
||||
<MudCheckBox T="bool" Label="@_dtf.GetMonthName(month)"
|
||||
Checked="@(_selectedItem.MonthsOfYear.Contains(month))"
|
||||
CheckedChanged="@(c => MonthOfYearChanged(month, c))"/>
|
||||
</MudElement>
|
||||
}
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(_ => SelectAllMonthsOfYear())">
|
||||
All
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(_ => SelectNoMonthsOfYear())">
|
||||
None
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</div>
|
||||
</div>
|
||||
</EditForm>
|
||||
}
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
private readonly CancellationTokenSource _cts = new();
|
||||
|
||||
private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentUICulture.DateTimeFormat;
|
||||
|
||||
[Parameter]
|
||||
public int Id { get; set; }
|
||||
|
||||
private string _channelName;
|
||||
private List<PlayoutAlternateScheduleEditViewModel> _items;
|
||||
private List<ProgramScheduleViewModel> _schedules;
|
||||
|
||||
private PlayoutAlternateScheduleEditViewModel _selectedItem;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cts.Cancel();
|
||||
_cts.Dispose();
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync() => await LoadScheduleItems();
|
||||
|
||||
private async Task LoadScheduleItems()
|
||||
{
|
||||
_schedules = await Mediator.Send(new GetAllProgramSchedules(), _cts.Token)
|
||||
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
|
||||
|
||||
_channelName = (await Mediator.Send(new GetChannelNameByPlayoutId(Id), _cts.Token)).IfNone(string.Empty);
|
||||
|
||||
List<PlayoutAlternateScheduleViewModel> results = await Mediator.Send(new GetPlayoutAlternateSchedules(Id), _cts.Token);
|
||||
_items = results.Map(ProjectToEditViewModel).ToList();
|
||||
if (_items.Count == 1)
|
||||
{
|
||||
_selectedItem = _items.Head();
|
||||
}
|
||||
}
|
||||
|
||||
private PlayoutAlternateScheduleEditViewModel ProjectToEditViewModel(PlayoutAlternateScheduleViewModel item) =>
|
||||
new()
|
||||
{
|
||||
Id = item.Id,
|
||||
Index = item.Index,
|
||||
ProgramSchedule = _schedules.Find(vm => vm.Id == item.ProgramScheduleId),
|
||||
DaysOfWeek = item.DaysOfWeek.OrderBy(x => ((int)x + 6) % 7).ToList(),
|
||||
DaysOfMonth = item.DaysOfMonth.ToList(),
|
||||
MonthsOfYear = item.MonthsOfYear.ToList()
|
||||
};
|
||||
|
||||
private void DayOfWeekChanged(DayOfWeek dayOfWeek, bool isChecked)
|
||||
{
|
||||
if (isChecked && !_selectedItem.DaysOfWeek.Contains(dayOfWeek))
|
||||
{
|
||||
_selectedItem.DaysOfWeek.Add(dayOfWeek);
|
||||
_selectedItem.DaysOfWeek = _selectedItem.DaysOfWeek.OrderBy(x => ((int)x + 6) % 7).ToList();
|
||||
}
|
||||
|
||||
if (!isChecked)
|
||||
{
|
||||
_selectedItem.DaysOfWeek.Remove(dayOfWeek);
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectWeekdays()
|
||||
{
|
||||
_selectedItem.DaysOfWeek.Clear();
|
||||
_selectedItem.DaysOfWeek.AddRange(new[]
|
||||
{
|
||||
DayOfWeek.Monday,
|
||||
DayOfWeek.Tuesday,
|
||||
DayOfWeek.Wednesday,
|
||||
DayOfWeek.Thursday,
|
||||
DayOfWeek.Friday
|
||||
});
|
||||
}
|
||||
|
||||
private void SelectWeekends()
|
||||
{
|
||||
_selectedItem.DaysOfWeek.Clear();
|
||||
_selectedItem.DaysOfWeek.AddRange(new[]
|
||||
{
|
||||
DayOfWeek.Saturday,
|
||||
DayOfWeek.Sunday
|
||||
});
|
||||
}
|
||||
|
||||
private void SelectAllDaysOfWeek()
|
||||
{
|
||||
_selectedItem.DaysOfWeek.Clear();
|
||||
_selectedItem.DaysOfWeek.AddRange(ProgramScheduleAlternate.AllDaysOfWeek());
|
||||
}
|
||||
|
||||
private void SelectNoDaysOfWeek()
|
||||
{
|
||||
_selectedItem.DaysOfWeek.Clear();
|
||||
}
|
||||
|
||||
private void DayOfMonthChanged(int dayOfMonth, bool isChecked)
|
||||
{
|
||||
if (isChecked && !_selectedItem.DaysOfMonth.Contains(dayOfMonth))
|
||||
{
|
||||
_selectedItem.DaysOfMonth.Add(dayOfMonth);
|
||||
_selectedItem.DaysOfMonth.Sort();
|
||||
}
|
||||
|
||||
if (!isChecked)
|
||||
{
|
||||
_selectedItem.DaysOfMonth.Remove(dayOfMonth);
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectAllDaysOfMonth()
|
||||
{
|
||||
_selectedItem.DaysOfMonth.Clear();
|
||||
_selectedItem.DaysOfMonth.AddRange(ProgramScheduleAlternate.AllDaysOfMonth());
|
||||
}
|
||||
|
||||
private void SelectNoDaysOfMonth()
|
||||
{
|
||||
_selectedItem.DaysOfMonth.Clear();
|
||||
}
|
||||
|
||||
private void MonthOfYearChanged(int monthOfYear, bool isChecked)
|
||||
{
|
||||
if (isChecked && !_selectedItem.MonthsOfYear.Contains(monthOfYear))
|
||||
{
|
||||
_selectedItem.MonthsOfYear.Add(monthOfYear);
|
||||
_selectedItem.MonthsOfYear.Sort();
|
||||
}
|
||||
|
||||
if (!isChecked)
|
||||
{
|
||||
_selectedItem.MonthsOfYear.Remove(monthOfYear);
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectAllMonthsOfYear()
|
||||
{
|
||||
_selectedItem.MonthsOfYear.Clear();
|
||||
_selectedItem.MonthsOfYear.AddRange(ProgramScheduleAlternate.AllMonthsOfYear());
|
||||
}
|
||||
|
||||
private void SelectNoMonthsOfYear()
|
||||
{
|
||||
_selectedItem.MonthsOfYear.Clear();
|
||||
}
|
||||
|
||||
private void AddAlternateSchedule()
|
||||
{
|
||||
var item = new PlayoutAlternateScheduleEditViewModel
|
||||
{
|
||||
Index = _items.Map(i => i.Index).DefaultIfEmpty().Max() + 1,
|
||||
ProgramSchedule = _schedules.Head(),
|
||||
DaysOfWeek = ProgramScheduleAlternate.AllDaysOfWeek(),
|
||||
DaysOfMonth = ProgramScheduleAlternate.AllDaysOfMonth(),
|
||||
MonthsOfYear = ProgramScheduleAlternate.AllMonthsOfYear()
|
||||
};
|
||||
|
||||
_items.Add(item);
|
||||
_selectedItem = item;
|
||||
}
|
||||
|
||||
private void RemoveAlternateSchedule(PlayoutAlternateScheduleEditViewModel item)
|
||||
{
|
||||
_selectedItem = null;
|
||||
_items.Remove(item);
|
||||
}
|
||||
|
||||
private void MoveItemUp(PlayoutAlternateScheduleEditViewModel item)
|
||||
{
|
||||
// swap with lower index
|
||||
PlayoutAlternateScheduleEditViewModel toSwap = _items.OrderByDescending(x => x.Index).First(x => x.Index < item.Index);
|
||||
(toSwap.Index, item.Index) = (item.Index, toSwap.Index);
|
||||
}
|
||||
|
||||
private void MoveItemDown(PlayoutAlternateScheduleEditViewModel item)
|
||||
{
|
||||
// swap with higher index
|
||||
PlayoutAlternateScheduleEditViewModel toSwap = _items.OrderBy(x => x.Index).First(x => x.Index > item.Index);
|
||||
(toSwap.Index, item.Index) = (item.Index, toSwap.Index);
|
||||
}
|
||||
|
||||
private async Task SaveChanges()
|
||||
{
|
||||
var items = _items.Map(item => new ReplacePlayoutAlternateSchedule(
|
||||
item.Id,
|
||||
item.Index,
|
||||
item.ProgramSchedule.Id,
|
||||
item.DaysOfWeek,
|
||||
item.DaysOfMonth,
|
||||
item.MonthsOfYear)).ToList();
|
||||
|
||||
Seq<BaseError> errorMessages = await Mediator.Send(new ReplacePlayoutAlternateScheduleItems(Id, items), _cts.Token)
|
||||
.Map(e => e.LeftToSeq());
|
||||
|
||||
errorMessages.HeadOrNone().Match(
|
||||
error =>
|
||||
{
|
||||
Snackbar.Add($"Unexpected error saving alternate schedules: {error.Value}", Severity.Error);
|
||||
Logger.LogError("Unexpected error saving alternate schedules: {Error}", error.Value);
|
||||
},
|
||||
() => NavigationManager.NavigateTo("/playouts"));
|
||||
}
|
||||
|
||||
private string ToDaysOfWeekString(List<DayOfWeek> daysOfWeek)
|
||||
{
|
||||
if (daysOfWeek.Count is 0 or 7)
|
||||
{
|
||||
return "*any*";
|
||||
}
|
||||
|
||||
daysOfWeek.Sort();
|
||||
|
||||
return string.Join(", ", daysOfWeek.Map(_dtf.GetAbbreviatedDayName));
|
||||
}
|
||||
|
||||
private string ToDaysOfMonthString(List<int> daysOfMonth)
|
||||
{
|
||||
if (daysOfMonth.Count is 0 or 31)
|
||||
{
|
||||
return "*any*";
|
||||
}
|
||||
|
||||
return ToRangeString(daysOfMonth);
|
||||
}
|
||||
|
||||
private string ToMonthsOfYearString(List<int> monthsOfYear)
|
||||
{
|
||||
if (monthsOfYear.Count is 0 or 12)
|
||||
{
|
||||
return "*any*";
|
||||
}
|
||||
|
||||
monthsOfYear.Sort();
|
||||
|
||||
return string.Join(", ", monthsOfYear.Map(_dtf.GetAbbreviatedMonthName));
|
||||
}
|
||||
|
||||
private static string ToRangeString(List<int> list)
|
||||
{
|
||||
list = list.Distinct().ToList();
|
||||
list.Sort();
|
||||
|
||||
var result = new StringBuilder();
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
int temp = list[i];
|
||||
|
||||
//add a number
|
||||
result.Append(list[i]);
|
||||
|
||||
//skip number(s) between a range
|
||||
while (i < list.Count - 1 && list[i + 1] == list[i] + 1)
|
||||
{
|
||||
i++;
|
||||
}
|
||||
|
||||
//add the range
|
||||
if (temp != list[i])
|
||||
{
|
||||
result.Append("-").Append(list[i]);
|
||||
}
|
||||
|
||||
//add comma
|
||||
if (i != list.Count - 1)
|
||||
{
|
||||
result.Append(", ");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,7 +33,7 @@
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel SortBy="new Func<PlayoutViewModel, object>(x => x.ProgramSchedule.Name)">
|
||||
Schedule
|
||||
Default Schedule
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
@* <MudTh>Playout Type</MudTh> *@
|
||||
@@ -45,6 +45,11 @@
|
||||
@* <MudTd DataLabel="Playout Type">@context.ProgramSchedulePlayoutType</MudTd> *@
|
||||
<MudTd>
|
||||
<div style="align-items: center; display: flex;">
|
||||
<MudTooltip Text="Edit Alternate Schedules">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.EditCalendar"
|
||||
Link="@($"playouts/{context.PlayoutId}/alternate-schedules")">
|
||||
</MudIconButton>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Reset Playout">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Refresh"
|
||||
OnClick="@(_ => ResetPlayout(context))">
|
||||
@@ -168,7 +173,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task ResetPlayout(PlayoutNameViewModel playout)
|
||||
{
|
||||
await _mediator.Send(new BuildPlayout(playout.PlayoutId, PlayoutBuildMode.Reset), _cts.Token);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using ErsatzTV.ViewModels;
|
||||
using FluentValidation;
|
||||
|
||||
namespace ErsatzTV.Validators;
|
||||
|
||||
public class PlayoutAlternateScheduleEditViewModelValidator : AbstractValidator<PlayoutAlternateScheduleEditViewModel>
|
||||
{
|
||||
public PlayoutAlternateScheduleEditViewModelValidator()
|
||||
{
|
||||
RuleFor(p => p.ProgramSchedule).NotNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using ErsatzTV.Application.ProgramSchedules;
|
||||
|
||||
namespace ErsatzTV.ViewModels;
|
||||
|
||||
public class PlayoutAlternateScheduleEditViewModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int Index { get; set; }
|
||||
public ProgramScheduleViewModel ProgramSchedule { get; set; }
|
||||
public List<DayOfWeek> DaysOfWeek { get; set; }
|
||||
public List<int> DaysOfMonth { get; set; }
|
||||
public List<int> MonthsOfYear { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user