adjust block unique constraint (#1634)
* upgrade dependencies * allow blocks with same name in different groups * code cleanup
This commit is contained in:
@@ -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,
|
||||
|
||||
+3
-6
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user