Exemple #1
0
    private static Validation <BaseError, ProgramSchedule> PlaybackOrdersMustBeValid(
        ReplaceProgramScheduleItems request,
        ProgramSchedule programSchedule)
    {
        var keyOrders = new Dictionary <CollectionKey, System.Collections.Generic.HashSet <PlaybackOrder> >();

        foreach (ReplaceProgramScheduleItem item in request.Items)
        {
            var key = new CollectionKey(
                item.CollectionType,
                item.CollectionId,
                item.MediaItemId,
                item.MultiCollectionId,
                item.SmartCollectionId);

            if (keyOrders.TryGetValue(key, out System.Collections.Generic.HashSet <PlaybackOrder> playbackOrders))
            {
                playbackOrders.Add(item.PlaybackOrder);
                keyOrders[key] = playbackOrders;
            }
            else
            {
                keyOrders.Add(key, new System.Collections.Generic.HashSet <PlaybackOrder> {
                    item.PlaybackOrder
                });
            }
        }

        return(Optional(keyOrders.Values.Count(set => set.Count != 1))
               .Filter(count => count == 0)
               .Map(_ => programSchedule)
               .ToValidation <BaseError>("A collection must not use multiple playback orders"));
    }
Exemple #2
0
    public async Task <Option <string> > GetNameFromKey(CollectionKey emptyCollection)
    {
        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();

        return(emptyCollection.CollectionType switch
        {
            ProgramScheduleItemCollectionType.Artist => await dbContext.Artists.Include(a => a.ArtistMetadata)
            .SelectOneAsync(a => a.Id, a => a.Id == emptyCollection.MediaItemId.Value)
            .MapT(a => a.ArtistMetadata.Head().Title),
            ProgramScheduleItemCollectionType.Collection => await dbContext.Collections
            .SelectOneAsync(c => c.Id, c => c.Id == emptyCollection.CollectionId.Value)
            .MapT(c => c.Name),
            ProgramScheduleItemCollectionType.MultiCollection => await dbContext.MultiCollections
            .SelectOneAsync(c => c.Id, c => c.Id == emptyCollection.MultiCollectionId.Value)
            .MapT(c => c.Name),
            ProgramScheduleItemCollectionType.SmartCollection => await dbContext.SmartCollections
            .SelectOneAsync(c => c.Id, c => c.Id == emptyCollection.SmartCollectionId.Value)
            .MapT(c => c.Name),
            ProgramScheduleItemCollectionType.TelevisionSeason => await dbContext.Seasons
            .Include(s => s.SeasonMetadata)
            .Include(s => s.Show)
            .ThenInclude(s => s.ShowMetadata)
            .SelectOneAsync(a => a.Id, a => a.Id == emptyCollection.MediaItemId.Value)
            .MapT(s => $"{s.Show.ShowMetadata.Head().Title} Season {s.SeasonNumber}"),
            ProgramScheduleItemCollectionType.TelevisionShow => await dbContext.Shows.Include(s => s.ShowMetadata)
            .SelectOneAsync(a => a.Id, a => a.Id == emptyCollection.MediaItemId.Value)
            .MapT(s => s.ShowMetadata.Head().Title),
            _ => None
        });
Exemple #3
0
 /// <summary>
 /// The inverse of {@link #registerLoadingCollectionXRef}.  Here, we are done
 /// processing the said collection entry, so we remove it from the
 /// load context.
 /// </summary>
 /// <param name="key">The key of the collection we are done processing. </param>
 /// <remarks>
 /// The idea here is that other loading collections can now reference said
 /// collection directly from the {@link PersistenceContext} because it
 /// has completed its load cycle.
 /// Implementation note: package protected, as this is meant solely for use
 /// by {@link CollectionLoadContext} to be able to locate collections
 /// being loaded by other {@link CollectionLoadContext}s/{@link ResultSet}s.
 /// </remarks>
 internal void UnregisterLoadingCollectionXRef(CollectionKey key)
 {
     if (!HasRegisteredLoadingCollectionEntries)
     {
         return;
     }
     xrefLoadingCollectionEntries.Remove(key);
 }
Exemple #4
0
        /// <summary>
        /// Register a loading collection xref.
        /// </summary>
        /// <param name="entryKey">The xref collection key </param>
        /// <param name="entry">The corresponding loading collection entry </param>
        /// <remarks>
        /// This xref map is used because sometimes a collection is in process of
        /// being loaded from one result set, but needs to be accessed from the
        /// context of another "nested" result set processing.
        /// Implementation note: package protected, as this is meant solely for use
        /// by {@link CollectionLoadContext} to be able to locate collections
        /// being loaded by other {@link CollectionLoadContext}s/{@link ResultSet}s.
        /// </remarks>
        internal void RegisterLoadingCollectionXRef(CollectionKey entryKey, LoadingCollectionEntry entry)
        {
            if (xrefLoadingCollectionEntries == null)
            {
                xrefLoadingCollectionEntries = new Dictionary <CollectionKey, LoadingCollectionEntry>();
            }

            xrefLoadingCollectionEntries[entryKey] = entry;
        }
    public static async Task <List <MediaItem> > Collect(
        IMediaCollectionRepository mediaCollectionRepository,
        ITelevisionRepository televisionRepository,
        IArtistRepository artistRepository,
        CollectionKey collectionKey)
    {
        switch (collectionKey.CollectionType)
        {
        case ProgramScheduleItemCollectionType.Collection:
            List <MediaItem> collectionItems =
                await mediaCollectionRepository.GetItems(collectionKey.CollectionId ?? 0);

            return(collectionItems);

        case ProgramScheduleItemCollectionType.TelevisionShow:
            List <Episode> showItems =
                await televisionRepository.GetShowItems(collectionKey.MediaItemId ?? 0);

            return(showItems.Cast <MediaItem>().ToList());

        case ProgramScheduleItemCollectionType.TelevisionSeason:
            List <Episode> seasonItems =
                await televisionRepository.GetSeasonItems(collectionKey.MediaItemId ?? 0);

            return(seasonItems.Cast <MediaItem>().ToList());

        case ProgramScheduleItemCollectionType.Artist:
            List <MusicVideo> artistItems =
                await artistRepository.GetArtistItems(collectionKey.MediaItemId ?? 0);

            return(artistItems.Cast <MediaItem>().ToList());

        case ProgramScheduleItemCollectionType.MultiCollection:
            List <MediaItem> multiCollectionItems =
                await mediaCollectionRepository.GetMultiCollectionItems(
                    collectionKey.MultiCollectionId ?? 0);

            return(multiCollectionItems);

        case ProgramScheduleItemCollectionType.SmartCollection:
            List <MediaItem> smartCollectionItems =
                await mediaCollectionRepository.GetSmartCollectionItems(
                    collectionKey.SmartCollectionId ?? 0);

            return(smartCollectionItems);

        default:
            return(new List <MediaItem>());
        }
    }
Exemple #6
0
        �������� /// <summary>
        �������� /// 1. Recreate the collection key -> collection map
        �������� /// 2. rebuild the collection entries
        �������� /// 3. call Interceptor.postFlush()
        �������� /// </summary>
        ��������protected virtual void PostFlush(ISessionImplementor session)
        ��������
        {
            ������������if(log.IsDebugEnabled)
            ������������ {
                ����������������log.Debug("post flush");
                ������������
            }
            �
            ������������IPersistenceContext persistenceContext = session.PersistenceContext;

            ������������persistenceContext.CollectionsByKey.Clear();
            ������������persistenceContext.BatchFetchQueue.ClearSubselects();
            ������������ //the database has changed now, so the subselect results need to be invalidated
            �
            ������������ // NH Different implementation: In NET an iterator is immutable;
            ������������ // we need something to hold the persistent collection to remove, and it must be less intrusive as possible
            ������������IDictionary cEntries = persistenceContext.CollectionEntries;
            ������������List <IPersistentCollection> keysToRemove = new List <IPersistentCollection>(cEntries.Count);

            ������������foreach(DictionaryEntry me in cEntries)
            ������������ {
                ����������������CollectionEntry       collectionEntry      = (CollectionEntry)me.Value;
                ����������������IPersistentCollection persistentCollection = (IPersistentCollection)me.Key;

                ����������������collectionEntry.PostFlush(persistentCollection);
                ����������������if(collectionEntry.LoadedPersister == null)
                ���������������� {
                    ��������������������keysToRemove.Add(persistentCollection);
                    ����������������
                }
                ����������������else
                ���������������� {
                    ��������������������//otherwise recreate the mapping between the collection and its key
                    ��������������������CollectionKey collectionKey =
                        ������������������������new CollectionKey(collectionEntry.LoadedPersister, collectionEntry.LoadedKey, session.EntityMode);

                    ��������������������persistenceContext.CollectionsByKey[collectionKey] = persistentCollection;
                    ����������������
                }
                ������������
            }
            ������������foreach(IPersistentCollection key in keysToRemove)
            ������������ {
                ����������������persistenceContext.CollectionEntries.Remove(key);
                ������������
            }
            ������������session.Interceptor.PostFlush((ICollection)persistenceContext.EntitiesByKey.Values);
            ��������
        }
        public void Should_Not_Crash_Mid_Roll_One_Chapter()
        {
            Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(1));

            var scheduleItem = new ProgramScheduleItemOne
            {
                Id             = 1,
                Index          = 1,
                Collection     = collectionOne,
                CollectionId   = collectionOne.Id,
                StartTime      = null,
                PlaybackOrder  = PlaybackOrder.Chronological,
                TailFiller     = null,
                FallbackFiller = null,
                MidRollFiller  = new FillerPreset
                {
                    FillerKind         = FillerKind.MidRoll,
                    FillerMode         = FillerMode.Pad,
                    PadToNearestMinute = 15
                }
            };

            var scheduleItemsEnumerator = new OrderedScheduleItemsEnumerator(
                new List <ProgramScheduleItem> {
                scheduleItem
            },
                new CollectionEnumeratorState());

            var enumerator = new ChronologicalMediaCollectionEnumerator(
                collectionOne.MediaItems,
                new CollectionEnumeratorState());

            PlayoutBuilderState startState = StartState(scheduleItemsEnumerator);

            Dictionary <CollectionKey, IMediaCollectionEnumerator> enumerators = CollectionEnumerators(
                scheduleItem,
                enumerator);

            // too lazy to make another enumerator for the filler that we don't want
            enumerators.Add(CollectionKey.ForFillerPreset(scheduleItem.MidRollFiller), enumerator);

            List <PlayoutItem> playoutItems = Scheduler()
                                              .AddFiller(
                startState,
                enumerators,
                scheduleItem,
                new PlayoutItem(),
                new List <MediaChapter> {
                new()
            });
        public void Should_Not_Touch_Enumerator()
        {
            var collection = new Collection
            {
                Id         = 1,
                Name       = "Filler Items",
                MediaItems = new List <MediaItem>()
            };

            for (var i = 0; i < 5; i++)
            {
                collection.MediaItems.Add(TestMovie(i + 1, TimeSpan.FromHours(i + 1), new DateTime(2020, 2, i + 1)));
            }

            var fillerPreset = new FillerPreset
            {
                FillerKind   = FillerKind.PreRoll,
                FillerMode   = FillerMode.Count,
                Count        = 3,
                Collection   = collection,
                CollectionId = collection.Id
            };

            var enumerator = new ChronologicalMediaCollectionEnumerator(
                collection.MediaItems,
                new CollectionEnumeratorState {
                Index = 0, Seed = 1
            });

            DateTimeOffset result = PlayoutModeSchedulerBase <ProgramScheduleItem>
                                    .CalculateEndTimeWithFiller(
                new Dictionary <CollectionKey, IMediaCollectionEnumerator>
            {
                { CollectionKey.ForFillerPreset(fillerPreset), enumerator }
            },
                new ProgramScheduleItemOne
            {
                PreRollFiller = fillerPreset
            },
                new DateTimeOffset(2020, 2, 1, 12, 0, 0, TimeSpan.FromHours(-5)),
                new TimeSpan(0, 12, 30),
                new List <MediaChapter>());

            result.Should().Be(new DateTimeOffset(2020, 2, 1, 18, 12, 30, TimeSpan.FromHours(-5)));
            enumerator.State.Index.Should().Be(0);
            enumerator.State.Seed.Should().Be(1);
        }
Exemple #9
0
        /// <summary>
        /// Locate the LoadingCollectionEntry within *any* of the tracked
        /// <see cref="CollectionLoadContext"/>s.
        /// </summary>
        /// <param name="key">The collection key. </param>
        /// <returns> The located entry; or null. </returns>
        /// <remarks>
        /// Implementation note: package protected, as this is meant solely for use
        /// by <see cref="CollectionLoadContext"/> to be able to locate collections
        /// being loaded by other <see cref="CollectionLoadContext"/>s/ResultSets.
        /// </remarks>
        internal LoadingCollectionEntry LocateLoadingCollectionEntry(CollectionKey key)
        {
            if (xrefLoadingCollectionEntries == null)
            {
                return(null);
            }
            if (log.IsDebugEnabled())
            {
                log.Debug("attempting to locate loading collection entry [{0}] in any result-set context", key);
            }
            LoadingCollectionEntry rtn;

            xrefLoadingCollectionEntries.TryGetValue(key, out rtn);
            if (log.IsDebugEnabled())
            {
                log.Debug("collection [{0}] {1} in load context", key, (rtn == null ? "located" : "not located"));
            }
            return(rtn);
        }
		public PersistentCollection GetLoadingCollection( ICollectionPersister persister, object id, object resultSetId )
		{
			CollectionKey ckey = new CollectionKey( persister, id );
			LoadingCollectionEntry lce = GetLoadingCollectionEntry( ckey );
			if( lce == null )
			{
				// look for existing collection
				PersistentCollection pc = GetCollection( ckey );
				if( pc != null )
				{
					CollectionEntry ce = GetCollectionEntry( pc );
					if( ce.initialized )
					{
						log.Debug( "collection already initialized: ignoring" );
						return null; //ignore this row of results! Note the early exit
					}
					else
					{
						log.Debug( "uninitialized collection: initializing" );
					}
				}
				else
				{
					object entity = GetCollectionOwner( id, persister );
					if( entity != null && GetEntry( entity ).Status != Status.Loading )
					{
						// important, to account for newly saved entities in query
						log.Debug( "owning entity already loaded: ignoring" );
						return null;
					}
					else
					{
						//create one
						log.Debug( "new collection: instantiating" );
						pc = persister.CollectionType.Instantiate( this, persister );
					}
				}
				pc.BeforeInitialize( persister );
				pc.BeginRead();
				AddLoadingCollectionEntry( ckey, pc, id, resultSetId );
				return pc;
			}
			else
			{
				if( lce.ResultSetId == resultSetId )
				{
					log.Debug( "reading row" );
					return lce.Collection;
				}
				else
				{
					//ignore this row, the collection is in process of being loaded somewhere further "up" the stack
					log.Debug( "collection is already being initialized: ignoring row" );
					return null;
				}
			}
		}
Exemple #11
0
    public override Tuple <PlayoutBuilderState, List <PlayoutItem> > Schedule(
        PlayoutBuilderState playoutBuilderState,
        Dictionary <CollectionKey, IMediaCollectionEnumerator> collectionEnumerators,
        ProgramScheduleItemOne scheduleItem,
        ProgramScheduleItem nextScheduleItem,
        DateTimeOffset hardStop)
    {
        IMediaCollectionEnumerator contentEnumerator =
            collectionEnumerators[CollectionKey.ForScheduleItem(scheduleItem)];

        foreach (MediaItem mediaItem in contentEnumerator.Current)
        {
            // find when we should start this item, based on the current time
            DateTimeOffset itemStartTime = GetStartTimeAfter(
                playoutBuilderState,
                scheduleItem);

            TimeSpan            itemDuration = DurationForMediaItem(mediaItem);
            List <MediaChapter> itemChapters = ChaptersForMediaItem(mediaItem);

            var playoutItem = new PlayoutItem
            {
                MediaItemId = mediaItem.Id,
                Start       = itemStartTime.UtcDateTime,
                Finish      = itemStartTime.UtcDateTime + itemDuration,
                InPoint     = TimeSpan.Zero,
                OutPoint    = itemDuration,
                GuideGroup  = playoutBuilderState.NextGuideGroup,
                FillerKind  = scheduleItem.GuideMode == GuideMode.Filler
                    ? FillerKind.Tail
                    : FillerKind.None,
                WatermarkId = scheduleItem.WatermarkId,
                PreferredAudioLanguageCode    = scheduleItem.PreferredAudioLanguageCode,
                PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode,
                SubtitleMode = scheduleItem.SubtitleMode
            };

            DateTimeOffset itemEndTimeWithFiller = CalculateEndTimeWithFiller(
                collectionEnumerators,
                scheduleItem,
                itemStartTime,
                itemDuration,
                itemChapters);

            List <PlayoutItem> playoutItems = AddFiller(
                playoutBuilderState,
                collectionEnumerators,
                scheduleItem,
                playoutItem,
                itemChapters);

            PlayoutBuilderState nextState = playoutBuilderState with
            {
                CurrentTime = itemEndTimeWithFiller
            };

            nextState.ScheduleItemsEnumerator.MoveNext();
            contentEnumerator.MoveNext();

            // LogScheduledItem(scheduleItem, mediaItem, itemStartTime);

            // only play one item from collection, so always advance to the next item
            // _logger.LogDebug(
            //     "Advancing to next schedule item after playout mode {PlayoutMode}",
            //     "One");

            DateTimeOffset nextItemStart = GetStartTimeAfter(nextState, nextScheduleItem);

            if (scheduleItem.TailFiller != null)
            {
                (nextState, playoutItems) = AddTailFiller(
                    nextState,
                    collectionEnumerators,
                    scheduleItem,
                    playoutItems,
                    nextItemStart);
            }

            if (scheduleItem.FallbackFiller != null)
            {
                (nextState, playoutItems) = AddFallbackFiller(
                    nextState,
                    collectionEnumerators,
                    scheduleItem,
                    playoutItems,
                    nextItemStart);
            }

            nextState = nextState with {
                NextGuideGroup = nextState.IncrementGuideGroup
            };

            return(Tuple(nextState, playoutItems));
        }

        return(Tuple(playoutBuilderState, new List <PlayoutItem>()));
    }
}
		private void AddCollection( PersistentCollection collection, CollectionEntry entry, object key )
		{
			collectionEntries[ collection ] = entry;

			CollectionKey ck = new CollectionKey( entry.loadedPersister, key );
			PersistentCollection old = ( PersistentCollection ) collectionsByKey[ ck ];
			collectionsByKey[ ck ] = collection;

			if( old != null )
			{
				if( old == collection )
				{
					throw new AssertionFailure( "collection added twice" );
				}
				// or should it actually throw an exception?
				old.UnsetSession( this );
				collectionEntries.Remove( old );
				// watch out for a case where old is still referenced
				// somewhere in the object graph! (which is a user error)
			}
		}
    public void Should_Not_Have_Gap_With_Unused_Tail_And_Unused_Fallback()
    {
        Collection collectionOne   = TwoItemCollection(1, 2, TimeSpan.FromHours(1));
        Collection collectionTwo   = TwoItemCollection(3, 4, TimeSpan.FromMinutes(4));
        Collection collectionThree = TwoItemCollection(5, 6, TimeSpan.FromMinutes(1));

        var scheduleItem = new ProgramScheduleItemMultiple
        {
            Id            = 1,
            Index         = 1,
            Collection    = collectionOne,
            CollectionId  = collectionOne.Id,
            StartTime     = null,
            PlaybackOrder = PlaybackOrder.Chronological,
            TailFiller    = new FillerPreset
            {
                FillerKind   = FillerKind.Tail,
                Collection   = collectionTwo,
                CollectionId = collectionTwo.Id
            },
            FallbackFiller = new FillerPreset
            {
                FillerKind   = FillerKind.Fallback,
                Collection   = collectionThree,
                CollectionId = collectionThree.Id
            },
            Count = 3
        };

        var scheduleItemsEnumerator = new OrderedScheduleItemsEnumerator(
            new List <ProgramScheduleItem> {
            scheduleItem
        },
            new CollectionEnumeratorState());

        var enumerator1 = new ChronologicalMediaCollectionEnumerator(
            collectionOne.MediaItems,
            new CollectionEnumeratorState());

        var enumerator2 = new ChronologicalMediaCollectionEnumerator(
            collectionTwo.MediaItems,
            new CollectionEnumeratorState());

        var enumerator3 = new ChronologicalMediaCollectionEnumerator(
            collectionThree.MediaItems,
            new CollectionEnumeratorState());

        var collectionMediaItems = new Dictionary <CollectionKey, List <MediaItem> >
        {
            { CollectionKey.ForScheduleItem(scheduleItem), collectionOne.MediaItems },
            { CollectionKey.ForFillerPreset(scheduleItem.TailFiller), collectionTwo.MediaItems },
            { CollectionKey.ForFillerPreset(scheduleItem.FallbackFiller), collectionThree.MediaItems }
        }.ToMap();

        PlayoutBuilderState startState = StartState(scheduleItemsEnumerator);

        var scheduler = new PlayoutModeSchedulerMultiple(collectionMediaItems, new Mock <ILogger>().Object);

        (PlayoutBuilderState playoutBuilderState, List <PlayoutItem> playoutItems) = scheduler.Schedule(
            startState,
            CollectionEnumerators(
                scheduleItem,
                enumerator1,
                scheduleItem.TailFiller,
                enumerator2,
                scheduleItem.FallbackFiller,
                enumerator3),
            scheduleItem,
            NextScheduleItem,
            HardStop(scheduleItemsEnumerator));

        playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
        playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);

        playoutBuilderState.NextGuideGroup.Should().Be(4);
        playoutBuilderState.DurationFinish.IsNone.Should().BeTrue();
        playoutBuilderState.InFlood.Should().BeFalse();
        playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue();
        playoutBuilderState.InDurationFiller.Should().BeFalse();
        playoutBuilderState.ScheduleItemsEnumerator.State.Index.Should().Be(0);

        enumerator1.State.Index.Should().Be(1);
        enumerator2.State.Index.Should().Be(0);
        enumerator3.State.Index.Should().Be(0);

        playoutItems.Count.Should().Be(3);

        playoutItems[0].MediaItemId.Should().Be(1);
        playoutItems[0].StartOffset.Should().Be(startState.CurrentTime);
        playoutItems[0].GuideGroup.Should().Be(1);
        playoutItems[0].FillerKind.Should().Be(FillerKind.None);

        playoutItems[1].MediaItemId.Should().Be(2);
        playoutItems[1].StartOffset.Should().Be(startState.CurrentTime.AddHours(1));
        playoutItems[1].GuideGroup.Should().Be(2);
        playoutItems[1].FillerKind.Should().Be(FillerKind.None);

        playoutItems[2].MediaItemId.Should().Be(1);
        playoutItems[2].StartOffset.Should().Be(startState.CurrentTime.AddHours(2));
        playoutItems[2].GuideGroup.Should().Be(3);
        playoutItems[2].FillerKind.Should().Be(FillerKind.None);
    }
		/// <summary> 
		/// 1. Recreate the collection key -> collection map
		/// 2. rebuild the collection entries
		/// 3. call Interceptor.postFlush()
		/// </summary>
		protected virtual void PostFlush(ISessionImplementor session)
		{
			if (log.IsDebugEnabled)
				log.Debug("post flush");

			IPersistenceContext persistenceContext = session.PersistenceContext;
			persistenceContext.CollectionsByKey.Clear();
			persistenceContext.BatchFetchQueue.ClearSubselects();
				//the database has changed now, so the subselect results need to be invalidated

			ISet keysToRemove = new HashedSet();
			IDictionary cEntries = persistenceContext.CollectionEntries;
			foreach (DictionaryEntry me in cEntries)
			{
				CollectionEntry collectionEntry = (CollectionEntry) me.Value;
				IPersistentCollection persistentCollection = (IPersistentCollection) me.Key;
				collectionEntry.PostFlush(persistentCollection);
				if (collectionEntry.LoadedPersister == null)
				{
					keysToRemove.Add(persistentCollection);
				}
				else
				{
					//otherwise recreate the mapping between the collection and its key
					CollectionKey collectionKey = new CollectionKey(collectionEntry.LoadedPersister, collectionEntry.LoadedKey, session.EntityMode);
					persistenceContext.CollectionsByKey[collectionKey] = persistentCollection;
				}
			}
			foreach (object key in keysToRemove)
			{
				persistenceContext.CollectionEntries.Remove(key);
			}
			session.Interceptor.PostFlush((ICollection)persistenceContext.EntitiesByKey.Values);
		}
    public override Tuple <PlayoutBuilderState, List <PlayoutItem> > Schedule(
        PlayoutBuilderState playoutBuilderState,
        Dictionary <CollectionKey, IMediaCollectionEnumerator> collectionEnumerators,
        ProgramScheduleItemFlood scheduleItem,
        ProgramScheduleItem nextScheduleItem,
        DateTimeOffset hardStop)
    {
        var playoutItems = new List <PlayoutItem>();

        PlayoutBuilderState nextState = playoutBuilderState;
        var willFinishInTime          = true;

        IMediaCollectionEnumerator contentEnumerator =
            collectionEnumerators[CollectionKey.ForScheduleItem(scheduleItem)];

        ProgramScheduleItem peekScheduleItem = nextScheduleItem;

        while (contentEnumerator.Current.IsSome && nextState.CurrentTime < hardStop && willFinishInTime)
        {
            MediaItem mediaItem = contentEnumerator.Current.ValueUnsafe();

            // find when we should start this item, based on the current time
            DateTimeOffset      itemStartTime = GetStartTimeAfter(nextState, scheduleItem);
            TimeSpan            itemDuration  = DurationForMediaItem(mediaItem);
            List <MediaChapter> itemChapters  = ChaptersForMediaItem(mediaItem);

            var playoutItem = new PlayoutItem
            {
                MediaItemId = mediaItem.Id,
                Start       = itemStartTime.UtcDateTime,
                Finish      = itemStartTime.UtcDateTime + itemDuration,
                InPoint     = TimeSpan.Zero,
                OutPoint    = itemDuration,
                GuideGroup  = nextState.NextGuideGroup,
                FillerKind  = scheduleItem.GuideMode == GuideMode.Filler
                    ? FillerKind.Tail
                    : FillerKind.None,
                WatermarkId = scheduleItem.WatermarkId,
                PreferredAudioLanguageCode    = scheduleItem.PreferredAudioLanguageCode,
                PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode,
                SubtitleMode = scheduleItem.SubtitleMode
            };

            DateTimeOffset peekScheduleItemStart =
                peekScheduleItem.StartType == StartType.Fixed
                    ? GetStartTimeAfter(nextState with {
                InFlood = false
            }, peekScheduleItem)
                    : DateTimeOffset.MaxValue;

            DateTimeOffset itemEndTimeWithFiller = CalculateEndTimeWithFiller(
                collectionEnumerators,
                scheduleItem,
                itemStartTime,
                itemDuration,
                itemChapters);

            // if the next schedule item is supposed to start during this item,
            // don't schedule this item and just move on
            willFinishInTime = peekScheduleItemStart < itemStartTime ||
                               peekScheduleItemStart >= itemEndTimeWithFiller;

            if (willFinishInTime)
            {
                playoutItems.AddRange(
                    AddFiller(nextState, collectionEnumerators, scheduleItem, playoutItem, itemChapters));
                // LogScheduledItem(scheduleItem, mediaItem, itemStartTime);

                DateTimeOffset actualEndTime = playoutItems.Max(p => p.FinishOffset);
                if (Math.Abs((itemEndTimeWithFiller - actualEndTime).TotalSeconds) > 1)
                {
                    _logger.LogWarning(
                        "Filler prediction failure: predicted {PredictedDuration} doesn't match actual {ActualDuration}",
                        itemEndTimeWithFiller,
                        actualEndTime);

                    // _logger.LogWarning("Playout items: {@PlayoutItems}", playoutItems);
                }

                nextState = nextState with
                {
                    CurrentTime    = itemEndTimeWithFiller,
                    InFlood        = true,
                    NextGuideGroup = nextState.IncrementGuideGroup
                };

                contentEnumerator.MoveNext();
            }
        }

        // _logger.LogDebug(
        //     "Advancing to next schedule item after playout mode {PlayoutMode}",
        //     "Flood");

        nextState = nextState with
        {
            InFlood        = nextState.CurrentTime >= hardStop,
            NextGuideGroup = nextState.DecrementGuideGroup
        };

        nextState.ScheduleItemsEnumerator.MoveNext();

        ProgramScheduleItem peekItem      = nextScheduleItem;
        DateTimeOffset      peekItemStart = GetStartTimeAfter(nextState, peekItem);

        if (scheduleItem.TailFiller != null)
        {
            (nextState, playoutItems) = AddTailFiller(
                nextState,
                collectionEnumerators,
                scheduleItem,
                playoutItems,
                peekItemStart);
        }

        if (scheduleItem.FallbackFiller != null)
        {
            (nextState, playoutItems) = AddFallbackFiller(
                nextState,
                collectionEnumerators,
                scheduleItem,
                playoutItems,
                peekItemStart);
        }

        nextState = nextState with {
            NextGuideGroup = nextState.IncrementGuideGroup
        };

        return(Tuple(nextState, playoutItems));
    }
}
		/// <summary> Add a collection which has no owner loaded</summary>
		public void AddUnownedCollection(CollectionKey key, IPersistentCollection collection)
		{
			if (unownedCollections == null)
				unownedCollections = new Dictionary<CollectionKey, IPersistentCollection>(8);

			unownedCollections[key] = collection;
		}
		/// <summary> Add an collection to the cache, with a given collection entry. </summary>
		/// <param name="coll">The collection for which we are adding an entry.</param>
		/// <param name="entry">The entry representing the collection. </param>
		/// <param name="key">The key of the collection's entry. </param>
		private void AddCollection(IPersistentCollection coll, CollectionEntry entry, object key)
		{
			collectionEntries[coll] = entry;
			CollectionKey collectionKey = new CollectionKey(entry.LoadedPersister, key, session.EntityMode);
			IPersistentCollection tempObject;
			collectionsByKey.TryGetValue(collectionKey, out tempObject);
			collectionsByKey[collectionKey] = coll;
			IPersistentCollection old = tempObject;
			if (old != null)
			{
				if (old == coll)
				{
					throw new AssertionFailure("bug adding collection twice");
				}
				// or should it actually throw an exception?
				old.UnsetSession(session);
				collectionEntries.Remove(old);
				// watch out for a case where old is still referenced
				// somewhere in the object graph! (which is a user error)
			}
		}
		private LoadingCollectionEntry GetLoadingCollectionEntry( CollectionKey collectionKey )
		{
			return ( LoadingCollectionEntry ) loadingCollections[ collectionKey ];
		}
    private async Task <Either <BaseError, PlayoutItemWithPath> > CheckForFallbackFiller(
        TvContext dbContext,
        Channel channel,
        DateTimeOffset now)
    {
        // check for channel fallback
        Option <FillerPreset> maybeFallback = await dbContext.FillerPresets
                                              .SelectOneAsync(w => w.Id, w => w.Id == channel.FallbackFillerId);

        // then check for global fallback
        if (maybeFallback.IsNone)
        {
            maybeFallback = await dbContext.ConfigElements
                            .GetValue <int>(ConfigElementKey.FFmpegGlobalFallbackFillerId)
                            .BindT(fillerId => dbContext.FillerPresets.SelectOneAsync(w => w.Id, w => w.Id == fillerId));
        }

        foreach (FillerPreset fallbackPreset in maybeFallback)
        {
            // turn this into a playout item

            var collectionKey      = CollectionKey.ForFillerPreset(fallbackPreset);
            List <MediaItem> items = await MediaItemsForCollection.Collect(
                _mediaCollectionRepository,
                _televisionRepository,
                _artistRepository,
                collectionKey);

            // TODO: shuffle? does it really matter since we loop anyway
            MediaItem item = items[new Random().Next(items.Count)];

            Option <TimeSpan> maybeDuration = await dbContext.PlayoutItems
                                              .Filter(pi => pi.Playout.ChannelId == channel.Id)
                                              .Filter(pi => pi.Start > now.UtcDateTime)
                                              .OrderBy(pi => pi.Start)
                                              .FirstOrDefaultAsync()
                                              .Map(Optional)
                                              .MapT(pi => pi.StartOffset - now);

            MediaVersion version = item.GetHeadVersion();

            version.MediaFiles = await dbContext.MediaFiles
                                 .AsNoTracking()
                                 .Filter(mf => mf.MediaVersionId == version.Id)
                                 .ToListAsync();

            version.Streams = await dbContext.MediaStreams
                              .AsNoTracking()
                              .Filter(ms => ms.MediaVersionId == version.Id)
                              .ToListAsync();

            DateTimeOffset finish = maybeDuration.Match(
                // next playout item exists
                // loop until it starts
                now.Add,
                // no next playout item exists
                // loop for 5 minutes if less than 30s, otherwise play full item
                () => version.Duration < TimeSpan.FromSeconds(30)
                    ? now.AddMinutes(5)
                    : now.Add(version.Duration));

            var playoutItem = new PlayoutItem
            {
                MediaItem   = item,
                MediaItemId = item.Id,
                Start       = now.UtcDateTime,
                Finish      = finish.UtcDateTime,
                FillerKind  = FillerKind.Fallback,
                InPoint     = TimeSpan.Zero,
                OutPoint    = version.Duration
            };

            return(await ValidatePlayoutItemPath(playoutItem));
        }

        return(new UnableToLocatePlayoutItem());
    }
Exemple #20
0
        public void Process(System.Runtime.Serialization.StreamingContext context)
        {
            if (string.IsNullOrEmpty(base.Html))
            {
                string _tempFooterPadding = string.Empty;

                int LevelSize = 0;
                if (TotalLevels == 1)
                {
                    LevelSize = CollectionKey.Split(":-:").Length;
                }
                else
                {
                    LevelSize = TotalLevels;
                }

                for (int i = 0; i < LevelSize; i++)
                {
                    _tempFooterPadding += "<td>&nbsp;</td>";
                }
                if (IsMultiLevel)
                {
                    _tempFooterPadding += "<td>&nbsp;</td>";
                }
                _tempFooterPadding += "<td>&nbsp;</td>";//serial column
                if (ShowCheckbox)
                {
                    _tempFooterPadding += "<td>&nbsp;</td>";
                }
                string _tempFooterText = string.Empty;
                foreach (DVBaseColumn Column in TableColumns)
                {
                    var ColumnCulture = Column.GetColumnCultureInfo(CultureDetails);
                    if (Column.bVisible)
                    {
                        if ((Column is DVNumericColumn) && (Column as DVNumericColumn).Aggregate)
                        {
                            string _style = string.Empty;
                            if ((Column as DVNumericColumn).Align == Align.Left)
                            {
                                _style = "text-align:left;";
                            }
                            else if ((Column as DVNumericColumn).Align == Align.Right || (Column as DVNumericColumn).Align == Align.Auto)
                            {
                                _style = "text-align:right;";
                            }
                            else
                            {
                                _style = "text-align:center;";
                            }
                            _tempFooterText += "<td style=" + _style + "><b>" + (this.Aggregations[Column.Data].Sum).ToString("N", ColumnCulture.NumberFormat)
                                               + "</b></td>";
                        }
                        else
                        {
                            _tempFooterText += "<td>&nbsp;</td>";
                        }
                    }
                }

                //if (IsAutoGen)
                //    _tempFooterPadding += "<td>&nbsp;</td>";

                base.Html = string.Format("<tr class='group-sum' group='{0}'>{1}{2}</tr>", (this.IsMultiLevel) ? base.CurrentLevel.ToString() : 1.ToString(), _tempFooterPadding,
                                          _tempFooterText);
            }
        }
Exemple #21
0
    public override Tuple <PlayoutBuilderState, List <PlayoutItem> > Schedule(
        PlayoutBuilderState playoutBuilderState,
        Dictionary <CollectionKey, IMediaCollectionEnumerator> collectionEnumerators,
        ProgramScheduleItemDuration scheduleItem,
        ProgramScheduleItem nextScheduleItem,
        DateTimeOffset hardStop)
    {
        var playoutItems = new List <PlayoutItem>();

        PlayoutBuilderState nextState = playoutBuilderState;

        var willFinishInTime = true;
        Option <DateTimeOffset> durationUntil = None;

        IMediaCollectionEnumerator contentEnumerator =
            collectionEnumerators[CollectionKey.ForScheduleItem(scheduleItem)];

        while (contentEnumerator.Current.IsSome && nextState.CurrentTime < hardStop && willFinishInTime)
        {
            MediaItem mediaItem = contentEnumerator.Current.ValueUnsafe();

            // find when we should start this item, based on the current time
            DateTimeOffset itemStartTime = GetStartTimeAfter(nextState, scheduleItem);

            // remember when we need to finish this duration item
            if (nextState.DurationFinish.IsNone)
            {
                nextState = nextState with
                {
                    DurationFinish = itemStartTime + scheduleItem.PlayoutDuration
                };

                durationUntil = nextState.DurationFinish;
            }

            TimeSpan            itemDuration = DurationForMediaItem(mediaItem);
            List <MediaChapter> itemChapters = ChaptersForMediaItem(mediaItem);

            if (itemDuration > scheduleItem.PlayoutDuration)
            {
                _logger.LogWarning(
                    "Skipping playout item {Title} with duration {Duration} that is longer than schedule item duration {PlayoutDuration}",
                    PlayoutBuilder.DisplayTitle(mediaItem),
                    itemDuration,
                    scheduleItem.PlayoutDuration);

                contentEnumerator.MoveNext();
                continue;
            }

            var playoutItem = new PlayoutItem
            {
                MediaItemId = mediaItem.Id,
                Start       = itemStartTime.UtcDateTime,
                Finish      = itemStartTime.UtcDateTime + itemDuration,
                InPoint     = TimeSpan.Zero,
                OutPoint    = itemDuration,
                GuideGroup  = nextState.NextGuideGroup,
                FillerKind  = scheduleItem.GuideMode == GuideMode.Filler
                    ? FillerKind.Tail
                    : FillerKind.None,
                CustomTitle = scheduleItem.CustomTitle,
                WatermarkId = scheduleItem.WatermarkId,
                PreferredAudioLanguageCode    = scheduleItem.PreferredAudioLanguageCode,
                PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode,
                SubtitleMode = scheduleItem.SubtitleMode
            };

            durationUntil.Do(du => playoutItem.GuideFinish = du.UtcDateTime);

            DateTimeOffset durationFinish        = nextState.DurationFinish.IfNone(SystemTime.MaxValueUtc);
            DateTimeOffset itemEndTimeWithFiller = CalculateEndTimeWithFiller(
                collectionEnumerators,
                scheduleItem,
                itemStartTime,
                itemDuration,
                itemChapters);
            willFinishInTime = itemStartTime > durationFinish ||
                               itemEndTimeWithFiller <= durationFinish;
            if (willFinishInTime)
            {
                // LogScheduledItem(scheduleItem, mediaItem, itemStartTime);
                playoutItems.AddRange(
                    AddFiller(nextState, collectionEnumerators, scheduleItem, playoutItem, itemChapters));

                nextState = nextState with
                {
                    CurrentTime = itemEndTimeWithFiller,

                    // only bump guide group if we don't have a custom title
                    NextGuideGroup = string.IsNullOrWhiteSpace(scheduleItem.CustomTitle)
                        ? nextState.IncrementGuideGroup
                        : nextState.NextGuideGroup
                };

                contentEnumerator.MoveNext();
            }
            else
            {
                TimeSpan durationBlock = itemEndTimeWithFiller - itemStartTime;
                if (itemEndTimeWithFiller - itemStartTime > scheduleItem.PlayoutDuration)
                {
                    _logger.LogWarning(
                        "Unable to schedule duration block of {DurationBlock} which is longer than the configured playout duration {PlayoutDuration}",
                        durationBlock,
                        scheduleItem.PlayoutDuration);
                }

                nextState = nextState with
                {
                    DurationFinish = None
                };

                nextState.ScheduleItemsEnumerator.MoveNext();
            }
        }

        // this is needed when the duration finish exactly matches the hard stop
        if (nextState.DurationFinish.IsSome && nextState.CurrentTime == nextState.DurationFinish)
        {
            nextState = nextState with
            {
                DurationFinish = None
            };

            nextState.ScheduleItemsEnumerator.MoveNext();
        }

        if (playoutItems.Select(pi => pi.GuideGroup).Distinct().Count() != 1)
        {
            nextState = nextState with {
                NextGuideGroup = nextState.DecrementGuideGroup
            };
        }

        foreach (DateTimeOffset nextItemStart in durationUntil)
        {
            switch (scheduleItem.TailMode)
            {
            case TailMode.Filler:
                if (scheduleItem.TailFiller != null)
                {
                    (nextState, playoutItems) = AddTailFiller(
                        nextState,
                        collectionEnumerators,
                        scheduleItem,
                        playoutItems,
                        nextItemStart);
                }

                if (scheduleItem.FallbackFiller != null)
                {
                    (nextState, playoutItems) = AddFallbackFiller(
                        nextState,
                        collectionEnumerators,
                        scheduleItem,
                        playoutItems,
                        nextItemStart);
                }

                nextState = nextState with {
                    CurrentTime = nextItemStart
                };
                break;

            case TailMode.Offline:
                if (scheduleItem.FallbackFiller != null)
                {
                    (nextState, playoutItems) = AddFallbackFiller(
                        nextState,
                        collectionEnumerators,
                        scheduleItem,
                        playoutItems,
                        nextItemStart);
                }

                nextState = nextState with {
                    CurrentTime = nextItemStart
                };
                break;
            }
        }

        // clear guide finish on all but the last item
        var         all  = playoutItems.Filter(pi => pi.FillerKind == FillerKind.None).ToList();
        PlayoutItem last = all.OrderBy(pi => pi.FinishOffset).LastOrDefault();

        foreach (PlayoutItem item in all.Filter(pi => pi != last))
        {
            item.GuideFinish = null;
        }

        nextState = nextState with {
            NextGuideGroup = nextState.IncrementGuideGroup
        };

        return(Tuple(nextState, playoutItems));
    }
}
		private void AddLoadingCollectionEntry( CollectionKey key, PersistentCollection collection, object id, object resultSetId )
		{
			loadingCollections.Add( key, new LoadingCollectionEntry( key, collection, id, resultSetId ) );
		}
		/// <summary> 
		/// 1. Recreate the collection key -> collection map
		/// 2. rebuild the collection entries
		/// 3. call Interceptor.postFlush()
		/// </summary>
		protected virtual void PostFlush(ISessionImplementor session)
		{
			if (log.IsDebugEnabled)
			{
				log.Debug("post flush");
			}

			IPersistenceContext persistenceContext = session.PersistenceContext;
			persistenceContext.CollectionsByKey.Clear();
			persistenceContext.BatchFetchQueue.ClearSubselects();
			//the database has changed now, so the subselect results need to be invalidated

			// NH Different implementation: In NET an iterator is immutable;
			// we need something to hold the persistent collection to remove, and it must be less intrusive as possible
			IDictionary cEntries = persistenceContext.CollectionEntries;
			List<IPersistentCollection> keysToRemove = new List<IPersistentCollection>(cEntries.Count);
			foreach (DictionaryEntry me in cEntries)
			{
				CollectionEntry collectionEntry = (CollectionEntry) me.Value;
				IPersistentCollection persistentCollection = (IPersistentCollection) me.Key;
				collectionEntry.PostFlush(persistentCollection);
				if (collectionEntry.LoadedPersister == null)
				{
					keysToRemove.Add(persistentCollection);
				}
				else
				{
					//otherwise recreate the mapping between the collection and its key
					CollectionKey collectionKey =
						new CollectionKey(collectionEntry.LoadedPersister, collectionEntry.LoadedKey, session.EntityMode);
					persistenceContext.CollectionsByKey[collectionKey] = persistentCollection;
				}
			}
			foreach (IPersistentCollection key in keysToRemove)
			{
				persistenceContext.CollectionEntries.Remove(key);
			}
			session.Interceptor.PostFlush((ICollection) persistenceContext.EntitiesByKey.Values);
		}
        /// <summary>
        /// Retrieve the collection that is being loaded as part of processing this result set.
        /// </summary>
        /// <param name="persister">The persister for the collection being requested. </param>
        /// <param name="key">The key of the collection being requested. </param>
        /// <returns> The loading collection (see discussion above). </returns>
        /// <remarks>
        /// Basically, there are two valid return values from this method:<ul>
        /// <li>an instance of {@link PersistentCollection} which indicates to
        /// continue loading the result set row data into that returned collection
        /// instance; this may be either an instance already associated and in the
        /// midst of being loaded, or a newly instantiated instance as a matching
        /// associated collection was not found.</li>
        /// <li><i>null</i> indicates to ignore the corresponding result set row
        /// data relating to the requested collection; this indicates that either
        /// the collection was found to already be associated with the persistence
        /// context in a fully loaded state, or it was found in a loading state
        /// associated with another result set processing context.</li>
        /// </ul>
        /// </remarks>
        public IPersistentCollection GetLoadingCollection(ICollectionPersister persister, object key)
        {
            CollectionKey collectionKey = new CollectionKey(persister, key);

            if (log.IsDebugEnabled())
            {
                log.Debug("starting attempt to find loading collection [{0}]", MessageHelper.InfoString(persister.Role, key));
            }
            LoadingCollectionEntry loadingCollectionEntry = loadContexts.LocateLoadingCollectionEntry(collectionKey);

            if (loadingCollectionEntry == null)
            {
                // look for existing collection as part of the persistence context
                IPersistentCollection collection = loadContexts.PersistenceContext.GetCollection(collectionKey);
                if (collection != null)
                {
                    if (collection.WasInitialized)
                    {
                        log.Debug("collection already initialized; ignoring");
                        return(null);                        // ignore this row of results! Note the early exit
                    }
                    else
                    {
                        // initialize this collection
                        log.Debug("collection not yet initialized; initializing");
                    }
                }
                else
                {
                    object owner            = loadContexts.PersistenceContext.GetCollectionOwner(key, persister);
                    bool   newlySavedEntity = owner != null && loadContexts.PersistenceContext.GetEntry(owner).Status != Status.Loading;
                    if (newlySavedEntity)
                    {
                        // important, to account for newly saved entities in query
                        // todo : some kind of check for new status...
                        log.Debug("owning entity already loaded; ignoring");
                        return(null);
                    }
                    else
                    {
                        // create one
                        if (log.IsDebugEnabled())
                        {
                            // Do not log the resultSet as-is, it is an IEnumerable which may get enumerated by loggers.
                            // (Serilog does that.) See #1667.
                            log.Debug("instantiating new collection [key={0}, rs={1}]", key, resultSet.GetType());
                        }
                        collection = persister.CollectionType.Instantiate(loadContexts.PersistenceContext.Session, persister, key);
                    }
                }
                collection.BeforeInitialize(persister, -1);
                collection.BeginRead();
                localLoadingCollectionKeys.Add(collectionKey);
                loadContexts.RegisterLoadingCollectionXRef(collectionKey, new LoadingCollectionEntry(resultSet, persister, key, collection));
                return(collection);
            }
            else
            {
                if (loadingCollectionEntry.ResultSet == resultSet)
                {
                    log.Debug("found loading collection bound to current result set processing; reading row");
                    return(loadingCollectionEntry.Collection);
                }
                else
                {
                    // ignore this row, the collection is in process of
                    // being loaded somewhere further "up" the stack
                    log.Debug("collection is already being initialized; ignoring row");
                    return(null);
                }
            }
        }
		private PersistentCollection GetCollection( CollectionKey key )
		{
			return ( PersistentCollection ) collectionsByKey[ key ];
		}
		/// <summary> 
		/// Get and remove a collection whose owner is not yet loaded,
		/// when its owner is being loaded
		/// </summary>
		public IPersistentCollection UseUnownedCollection(CollectionKey key)
		{
			if (unownedCollections == null)
			{
				return null;
			}
			else
			{
				IPersistentCollection tempObject;
				if (unownedCollections.TryGetValue(key, out tempObject))
					unownedCollections.Remove(key);
				return tempObject;
			}
		}
    public void Should_Have_Gap_With_No_Tail_No_Fallback()
    {
        Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55));

        var scheduleItem = new ProgramScheduleItemMultiple
        {
            Id             = 1,
            Index          = 1,
            Collection     = collectionOne,
            CollectionId   = collectionOne.Id,
            StartTime      = null,
            PlaybackOrder  = PlaybackOrder.Chronological,
            TailFiller     = null,
            FallbackFiller = null,
            Count          = 3
        };

        var scheduleItemsEnumerator = new OrderedScheduleItemsEnumerator(
            new List <ProgramScheduleItem> {
            scheduleItem
        },
            new CollectionEnumeratorState());

        var enumerator = new ChronologicalMediaCollectionEnumerator(
            collectionOne.MediaItems,
            new CollectionEnumeratorState());

        var collectionMediaItems = new Dictionary <CollectionKey, List <MediaItem> >
        {
            { CollectionKey.ForScheduleItem(scheduleItem), collectionOne.MediaItems }
        }.ToMap();

        PlayoutBuilderState startState = StartState(scheduleItemsEnumerator);

        var scheduler = new PlayoutModeSchedulerMultiple(collectionMediaItems, new Mock <ILogger>().Object);

        (PlayoutBuilderState playoutBuilderState, List <PlayoutItem> playoutItems) = scheduler.Schedule(
            startState,
            CollectionEnumerators(scheduleItem, enumerator),
            scheduleItem,
            NextScheduleItem,
            HardStop(scheduleItemsEnumerator));

        playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.Add(new TimeSpan(2, 45, 0)));
        playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);

        playoutBuilderState.NextGuideGroup.Should().Be(4);
        playoutBuilderState.DurationFinish.IsNone.Should().BeTrue();
        playoutBuilderState.InFlood.Should().BeFalse();
        playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue();
        playoutBuilderState.InDurationFiller.Should().BeFalse();
        playoutBuilderState.ScheduleItemsEnumerator.State.Index.Should().Be(0);

        enumerator.State.Index.Should().Be(1);

        playoutItems.Count.Should().Be(3);

        playoutItems[0].MediaItemId.Should().Be(1);
        playoutItems[0].StartOffset.Should().Be(startState.CurrentTime);
        playoutItems[0].GuideGroup.Should().Be(1);
        playoutItems[0].FillerKind.Should().Be(FillerKind.None);

        playoutItems[1].MediaItemId.Should().Be(2);
        playoutItems[1].StartOffset.Should().Be(startState.CurrentTime.AddMinutes(55));
        playoutItems[1].GuideGroup.Should().Be(2);
        playoutItems[1].FillerKind.Should().Be(FillerKind.None);

        playoutItems[2].MediaItemId.Should().Be(1);
        playoutItems[2].StartOffset.Should().Be(startState.CurrentTime.Add(new TimeSpan(1, 50, 0)));
        playoutItems[2].GuideGroup.Should().Be(3);
        playoutItems[2].FillerKind.Should().Be(FillerKind.None);
    }
		/// <summary> Get the collection instance associated with the <tt>CollectionKey</tt></summary>
		public IPersistentCollection GetCollection(CollectionKey collectionKey)
		{
			IPersistentCollection result;
			if (collectionsByKey.TryGetValue(collectionKey, out result))
				return result;
			else
				return null;
		}
			internal LoadingCollectionEntry( CollectionKey key, PersistentCollection collection, object id, object resultSetId )
			{
				this.key = key;
				this.collection = collection;
				this.id = id;
				this.resultSetId = resultSetId;
			}