adjust block unique constraint (#1634)

* upgrade dependencies

* allow blocks with same name in different groups

* code cleanup
This commit is contained in:
Jason Dove
2024-03-05 10:39:06 -06:00
committed by GitHub
parent 70c4036dc9
commit 087901d177
294 changed files with 13512 additions and 1421 deletions
@@ -21,32 +21,32 @@ public record BlockKey
}
/// <summary>
/// Block Id
/// Block Id
/// </summary>
public int b { get; set; }
/// <summary>
/// Template Id
/// Template Id
/// </summary>
public int t { get; set; }
/// <summary>
/// Playout Template Id
/// Playout Template Id
/// </summary>
public int pt { get; set; }
/// <summary>
/// Block Date Updated Ticks
/// Block Date Updated Ticks
/// </summary>
public long bt { get; set; }
/// <summary>
/// Template Date Updated Ticks
/// Template Date Updated Ticks
/// </summary>
public long tt { get; set; }
/// <summary>
/// Playout Template Date Updated Ticks
/// Playout Template Date Updated Ticks
/// </summary>
public long ptt { get; set; }
}
@@ -7,6 +7,7 @@ using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Scheduling;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Map = LanguageExt.Map;
namespace ErsatzTV.Core.Scheduling.BlockScheduling;
@@ -24,7 +25,12 @@ public class BlockPlayoutBuilder(
NullValueHandling = NullValueHandling.Ignore
};
public virtual async Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken)
protected virtual ILogger Logger => logger;
public virtual async Task<Playout> Build(
Playout playout,
PlayoutBuildMode mode,
CancellationToken cancellationToken)
{
Logger.LogDebug(
"Building block playout {PlayoutId} for channel {ChannelNumber} - {ChannelName}",
@@ -57,7 +63,7 @@ public class BlockPlayoutBuilder(
// remove items without a block key (shouldn't happen often, just upgrades)
playout.Items.RemoveAll(i => !itemBlockKeys.ContainsKey(i));
// remove old items
// importantly, this should not remove their history
playout.Items.RemoveAll(i => i.FinishOffset < start);
@@ -132,7 +138,7 @@ public class BlockPlayoutBuilder(
historyKey,
collectionMediaItems);
bool pastTime = false;
var pastTime = false;
foreach (MediaItem mediaItem in enumerator.Current)
{
@@ -144,7 +150,7 @@ public class BlockPlayoutBuilder(
TimeSpan itemDuration = DurationForMediaItem(mediaItem);
var collectionKey = CollectionKey.ForBlockItem(blockItem);
// create a playout item
var playoutItem = new PlayoutItem
{
@@ -201,7 +207,7 @@ public class BlockPlayoutBuilder(
currentTime += itemDuration;
enumerator.MoveNext();
}
if (pastTime)
{
break;
@@ -214,14 +220,10 @@ public class BlockPlayoutBuilder(
return playout;
}
protected virtual ILogger Logger => logger;
protected virtual async Task<int> GetDaysToBuild()
{
return await configElementRepository
protected virtual async Task<int> GetDaysToBuild() =>
await configElementRepository
.GetValue<int>(ConfigElementKey.PlayoutDaysToBuild)
.IfNoneAsync(2);
}
protected virtual IMediaCollectionEnumerator GetEnumerator(
Playout playout,
@@ -331,7 +333,7 @@ public class BlockPlayoutBuilder(
artistRepository,
collectionKey))).SequenceParallel();
return LanguageExt.Map.createRange(tuples);
return Map.createRange(tuples);
}
private Map<CollectionKey, string> GetCollectionEtags(
@@ -2,6 +2,7 @@ using System.Collections.Immutable;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
using Newtonsoft.Json;
using Serilog;
namespace ErsatzTV.Core.Scheduling.BlockScheduling;
@@ -39,7 +40,7 @@ internal static class BlockPlayoutChangeDetection
var earliestEffectiveBlocks = new Dictionary<BlockKey, DateTimeOffset>();
var earliestBlocks = new Dictionary<int, DateTimeOffset>();
// check for changed collections
foreach (EffectiveBlock effectiveBlock in blocksToSchedule.OrderBy(b => b.Start))
{
@@ -54,18 +55,19 @@ internal static class BlockPlayoutChangeDetection
bool isUpdated = string.IsNullOrWhiteSpace(playoutItem.CollectionKey);
if (!isUpdated)
{
CollectionKey collectionKey = JsonConvert.DeserializeObject<CollectionKey>(playoutItem.CollectionKey);
CollectionKey collectionKey =
JsonConvert.DeserializeObject<CollectionKey>(playoutItem.CollectionKey);
// collection is no longer present or collection has been modified
isUpdated = !collectionEtags.ContainsKey(collectionKey) ||
collectionEtags[collectionKey] != playoutItem.CollectionEtag;
}
if (isUpdated)
{
// playout item needs to be removed/re-added
updatedItemIds.Add(playoutItem.Id);
// block needs to be scheduled again
updatedBlocks.Add(effectiveBlock);
@@ -114,12 +116,12 @@ internal static class BlockPlayoutChangeDetection
foreach ((BlockKey key, DateTimeOffset value) in earliestEffectiveBlocks)
{
Serilog.Log.Logger.Debug("Earliest effective block: {Key} => {Value}", key, value);
Log.Logger.Debug("Earliest effective block: {Key} => {Value}", key, value);
}
foreach ((int blockId, DateTimeOffset value) in earliestBlocks)
{
Serilog.Log.Logger.Debug("Earliest block id: {Id} => {Value}", blockId, value);
Log.Logger.Debug("Earliest block id: {Id} => {Value}", blockId, value);
}
// find affected playout items
@@ -131,8 +133,8 @@ internal static class BlockPlayoutChangeDetection
value <= item.StartOffset;
bool blockIdIsAffected = earliestBlocks.TryGetValue(blockKey.b, out DateTimeOffset value2) &&
value2 <= item.StartOffset;
value2 <= item.StartOffset;
if (!blockKeysToSchedule.Contains(blockKey) || blockKeyIsAffected || blockIdIsAffected)
{
updatedItemIds.Add(item.Id);
@@ -1,9 +1,7 @@
using System.Collections.Immutable;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Core.Interfaces.Scheduling;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace ErsatzTV.Core.Scheduling.BlockScheduling;
@@ -28,7 +26,7 @@ public static class BlockPlayoutEnumerator
var state = new CollectionEnumeratorState { Seed = 0, Index = 0 };
var enumerator = new ChronologicalMediaCollectionEnumerator(collectionItems, state);
// seek to the appropriate place in the collection enumerator
foreach (PlayoutHistory h in maybeHistory)
{
@@ -43,7 +41,7 @@ public static class BlockPlayoutEnumerator
return enumerator;
}
public static IMediaCollectionEnumerator SeasonEpisode(
List<MediaItem> collectionItems,
DateTimeOffset currentTime,
@@ -63,7 +61,7 @@ public static class BlockPlayoutEnumerator
var state = new CollectionEnumeratorState { Seed = 0, Index = 0 };
var enumerator = new SeasonEpisodeMediaCollectionEnumerator(collectionItems, state);
// seek to the appropriate place in the collection enumerator
foreach (PlayoutHistory h in maybeHistory)
{
@@ -78,7 +76,7 @@ public static class BlockPlayoutEnumerator
return enumerator;
}
public static IMediaCollectionEnumerator Shuffle(
List<MediaItem> collectionItems,
DateTimeOffset currentTime,
@@ -6,9 +6,9 @@ namespace ErsatzTV.Core.Scheduling.BlockScheduling;
public class BlockPlayoutShuffledMediaCollectionEnumerator : IMediaCollectionEnumerator
{
private readonly IList<GroupedMediaItem> _mediaItems;
private readonly Lazy<Option<TimeSpan>> _lazyMinimumDuration;
private readonly int _mediaItemCount;
private readonly IList<GroupedMediaItem> _mediaItems;
private IList<MediaItem> _shuffled;
public BlockPlayoutShuffledMediaCollectionEnumerator(
@@ -42,10 +42,7 @@ public class BlockPlayoutShuffledMediaCollectionEnumerator : IMediaCollectionEnu
public Option<MediaItem> Current => _shuffled.Any() ? _shuffled[State.Index % _mediaItemCount] : None;
public void MoveNext()
{
State.Index++;
}
public void MoveNext() => State.Index++;
public Option<TimeSpan> MinimumDuration => _lazyMinimumDuration.Value;
@@ -58,7 +55,7 @@ public class BlockPlayoutShuffledMediaCollectionEnumerator : IMediaCollectionEnu
var superShuffle = new SuperShuffle();
for (var i = 0; i < list.Count; i++)
{
int toSelect = superShuffle.Shuffle(i, State.Seed + (State.Index / list.Count), list.Count);
int toSelect = superShuffle.Shuffle(i, State.Seed + State.Index / list.Count, list.Count);
copy[i] = list[toSelect];
}
@@ -24,7 +24,7 @@ internal record EffectiveBlock(Block Block, BlockKey BlockKey, DateTimeOffset St
// playoutTemplate.Template.Name);
DateTimeOffset today = current;
var newBlocks = playoutTemplate.Template.Items
.Map(i => ToEffectiveBlock(playoutTemplate, i, today, start))
.Map(NormalizeGuideMode)
@@ -11,7 +11,7 @@ internal static class HistoryDetails
{
NullValueHandling = NullValueHandling.Ignore
};
public static string KeyForBlockItem(BlockItem blockItem)
{
dynamic key = new
@@ -36,7 +36,7 @@ internal static class HistoryDetails
Movie m => ForMovie(m),
_ => new Details(mediaItem.Id, null, null, null)
};
return JsonConvert.SerializeObject(details, Formatting.None, JsonSettings);
}
@@ -50,16 +50,16 @@ internal static class HistoryDetails
{
return;
}
Option<MediaItem> maybeMatchedItem = Option<MediaItem>.None;
var copy = collectionItems.ToList();
Details details = JsonConvert.DeserializeObject<Details>(detailsString);
if (details.SeasonNumber.HasValue && details.EpisodeNumber.HasValue)
{
int season = details.SeasonNumber.Value;
int episode = details.EpisodeNumber.Value;
maybeMatchedItem = Optional(collectionItems.Find(ci => MatchSeasonAndEpisode(ci, season, episode)));
if (maybeMatchedItem.IsNone)
@@ -93,13 +93,13 @@ internal static class HistoryDetails
}
}
// TODO: match media item
foreach (MediaItem matchedItem in maybeMatchedItem)
{
IComparer<MediaItem> comparer = playbackOrder switch
{
PlaybackOrder.Chronological => new ChronologicalMediaComparer(),
_ => new SeasonEpisodeMediaComparer(),
_ => new SeasonEpisodeMediaComparer()
};
copy.Sort(comparer);
+8 -8
View File
@@ -12,43 +12,43 @@ public class CollectionKey : Record<CollectionKey>
public int? SmartCollectionId { get; set; }
public int? MediaItemId { get; set; }
public string FakeCollectionKey { get; set; }
public static CollectionKey ForBlockItem(BlockItem item) =>
item.CollectionType switch
{
ProgramScheduleItemCollectionType.Collection => new CollectionKey
{
CollectionType = item.CollectionType,
CollectionId = item.CollectionId,
CollectionId = item.CollectionId
},
ProgramScheduleItemCollectionType.TelevisionShow => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.TelevisionSeason => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.Artist => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.MultiCollection => new CollectionKey
{
CollectionType = item.CollectionType,
MultiCollectionId = item.MultiCollectionId,
MultiCollectionId = item.MultiCollectionId
},
ProgramScheduleItemCollectionType.SmartCollection => new CollectionKey
{
CollectionType = item.CollectionType,
SmartCollectionId = item.SmartCollectionId,
SmartCollectionId = item.SmartCollectionId
},
ProgramScheduleItemCollectionType.FakeCollection => new CollectionKey
{
CollectionType = item.CollectionType,
CollectionType = item.CollectionType
},
_ => throw new ArgumentOutOfRangeException(nameof(item))
};
+3 -3
View File
@@ -53,7 +53,7 @@ public class PlayoutBuilder : IPlayoutBuilder
return playout;
}
foreach (PlayoutParameters parameters in await Validate(playout))
{
// for testing purposes
@@ -459,7 +459,7 @@ public class PlayoutBuilder : IPlayoutBuilder
var fakeCollections = _mediaCollectionRepository.GroupIntoFakeCollections(mediaItems, collectionKeyString)
.Filter(c => c.ShowId > 0 || c.ArtistId > 0 || !string.IsNullOrWhiteSpace(c.Key))
.ToList();
List<ProgramScheduleItem> fakeScheduleItems = [];
List<ProgramScheduleItem> fakeScheduleItems = [];
// this will be used to clone a schedule item
MethodInfo generic = typeof(JsonConvert).GetMethods()
@@ -495,7 +495,7 @@ public class PlayoutBuilder : IPlayoutBuilder
{
continue;
}
string serialized = JsonConvert.SerializeObject(scheduleItem);
var copyScheduleItem = generic.Invoke(this, [serialized]) as ProgramScheduleItem;
copyScheduleItem.CollectionType = key.CollectionType;
+42 -20
View File
@@ -4,29 +4,33 @@ namespace ErsatzTV.Core.Scheduling;
public class SuperShuffle
{
private int userSID = 1;
private int si, r1, r2, r3, r4;
private int randR, halfN, rx, rkey;
public int Shuffle(int inx, int shuffleId, int listSize) {
int si, hi, offset;
int halfSize = listSize / 2; // for 1/2 the processing range
private int si, r1, r2, r3, r4;
private readonly int userSID = 1;
shuffleId += 131 * (inx / listSize); // have inx overflows supported
si = (inx % listSize);
if (si < halfSize) {
public int Shuffle(int inx, int shuffleId, int listSize)
{
int si, hi, offset;
int halfSize = listSize / 2; // for 1/2 the processing range
shuffleId += 131 * (inx / listSize); // have inx overflows supported
si = inx % listSize;
if (si < halfSize)
{
hi = si;
offset = 0;
} else {
}
else
{
hi = listSize - 1 - si;
offset = halfSize;
halfSize = listSize - halfSize;
shuffleId++;
}
hi = MillerShuffleE(hi, shuffleId, halfSize); // use any STD MSA() shuffle (aka: a PRIG function)
si = MillerShuffleE(hi + offset, userSID, listSize); // indexing into the baseline shuffle
return(si);
hi = MillerShuffleE(hi, shuffleId, halfSize); // use any STD MSA() shuffle (aka: a PRIG function)
si = MillerShuffleE(hi + offset, userSID, listSize); // indexing into the baseline shuffle
return si;
}
private int MillerShuffleE(int inx, int shuffleId, int listSize)
@@ -42,23 +46,41 @@ public class SuperShuffle
r3 = randR % p2;
r4 = randR % 2749;
halfN = listSize / 2 + 1;
rx = (randR / listSize) % listSize + 1;
rkey = (randR / listSize / listSize) % listSize + 1;
rx = randR / listSize % listSize + 1;
rkey = randR / listSize / listSize % listSize + 1;
// perform the conditional multi-faceted mathematical mixing (on avg 2 5/6 shuffle ops done + 2 simple Xors)
if (si % 3 == 0) si = ((si / 3 * p1 + r1) % ((listSize + 2) / 3)) * 3; // spin multiples of 3
if (si % 3 == 0)
{
si = (si / 3 * p1 + r1) % ((listSize + 2) / 3) * 3; // spin multiples of 3
}
if (si <= halfN)
{
si = (si + r3) % (halfN + 1);
si = halfN - si;
} // improves large permu distro
if (si % 2 == 0) si = ((si / 2 * p2 + r2) % ((listSize + 1) / 2)) * 2; // spin multiples of 2
if (si < halfN) si = (si * p3 + r3) % halfN;
if (si % 2 == 0)
{
si = (si / 2 * p2 + r2) % ((listSize + 1) / 2) * 2; // spin multiples of 2
}
if (si < halfN)
{
si = (si * p3 + r3) % halfN;
}
if ((si ^ rx) < listSize)
{
si ^= rx; // flip some bits with Xor
}
if ((si ^ rx) < listSize) si ^= rx; // flip some bits with Xor
si = (si * p3 + r4) % listSize; // a relatively prime gears churning operation
if ((si ^ rkey) < listSize) si ^= rkey;
if ((si ^ rkey) < listSize)
{
si ^= rkey;
}
return si;
}