예제 #1
0
        public void No_Resource_Can_Be_Used_Twice()
        {
            // Create the component under test
            var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase );
            var resource = ResourceMock.Create( "a" );

            // Register devices
            componentUnderTest.Add( resource );
            componentUnderTest.Add( resource );
        }
예제 #2
0
        public void Individual_Decryption_Counter_Must_Not_Be_Negative()
        {
            // Create the component under test
            var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase );

            // Register device
            componentUnderTest.Add( ResourceMock.Create( "a" ).SetEncryptionLimit( -1 ) );
        }
예제 #3
0
        public void A_Resource_Must_Not_Be_Null()
        {
            // Create the component under test
            var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase );

            // Register a null resource
            componentUnderTest.Add( default( IScheduleResource ) );
        }
예제 #4
0
        public void Can_Add_Multiple_Resources()
        {
            // Create the component under test
            var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase );

            // Register devices
            for (int i = 0; i < 100; i++)
                componentUnderTest.Add( ResourceMock.Create( i.ToString( "00" ) ) );
        }
예제 #5
0
        /// <summary>
        /// Registriert diese Aufzeichnung in einer Planungsinstanz.
        /// </summary>
        /// <param name="scheduler">Die zu verwendende Planungsinstanz.</param>
        /// <param name="job">Der zugehörige Auftrag.</param>
        /// <param name="devices">Die Liste der Geräte, auf denen die Aufzeichnung ausgeführt werden darf.</param>
        /// <param name="findSource">Dient zum Prüfen einer Quelle.</param>
        /// <param name="disabled">Alle deaktivierten Aufträge.</param>
        /// <param name="context">Die aktuelle Planungsumgebung.</param>
        /// <exception cref="ArgumentNullException">Es wurden nicht alle Parameter angegeben.</exception>
        public void AddToScheduler( RecordingScheduler scheduler, VCRJob job, IScheduleResource[] devices, Func<SourceSelection, SourceSelection> findSource, Func<Guid, bool> disabled, PlanContext context )
        {
            // Validate
            if (scheduler == null)
                throw new ArgumentNullException( nameof( scheduler ) );
            if (job == null)
                throw new ArgumentNullException( nameof( job ) );
            if (findSource == null)
                throw new ArgumentNullException( nameof( findSource ) );

            // Let VCR.NET choose a profile to do the work
            if (job.AutomaticResourceSelection)
                devices = null;

            // Create the source selection
            var persistedSource = Source ?? job.Source;
            var selection = findSource( persistedSource );

            // Station no longer available
            if (selection == null)
                if (persistedSource != null)
                    selection =
                        new SourceSelection
                        {
                            DisplayName = persistedSource.DisplayName,
                            ProfileName = persistedSource.ProfileName,
                            Location = persistedSource.Location,
                            Group = persistedSource.Group,
                            Source =
                                new Station
                                {
                                    TransportStream = persistedSource.Source?.TransportStream ?? 0,
                                    Network = persistedSource.Source?.Network ?? 0,
                                    Service = persistedSource.Source?.Service ?? 0,
                                    Name = persistedSource.DisplayName,
                                },
                        };

            // See if we are allowed to process
            var identifier = UniqueID.Value;
            if (disabled != null)
                if (disabled( identifier ))
                    return;

            // Load all
            var name = string.IsNullOrEmpty( Name ) ? job.Name : $"{job.Name} ({Name})";
            var source = ProfileScheduleResource.CreateSource( selection );
            var duration = TimeSpan.FromMinutes( Duration );
            var noStartBefore = NoStartBefore;
            var start = FirstStart;

            // Check repetition
            var repeat = CreateRepeatPattern();
            if (repeat == null)
            {
                // Only if not being recorded
                if (!noStartBefore.HasValue)
                    scheduler.Add( RecordingDefinition.Create( this, name, identifier, devices, source, start, duration ) );
            }
            else
            {
                // See if we have to adjust the start day
                if (noStartBefore.HasValue)
                {
                    // Attach to the limit - actually we shift it a bit further assuming that we did have no large exception towards the past and the duration is moderate
                    var startAfter = noStartBefore.Value.AddHours( 12 );
                    var startAfterDay = startAfter.ToLocalTime().Date;

                    // Localize the start time
                    var startTime = start.ToLocalTime().TimeOfDay;

                    // First adjust
                    start = (startAfterDay + startTime).ToUniversalTime();

                    // One more day
                    if (start < startAfter)
                        start = (startAfterDay.AddDays( 1 ) + startTime).ToUniversalTime();
                }

                // Read the rest
                var exceptions = Exceptions.Select( e => e.ToPlanException( duration ) ).ToArray();
                var endDay = LastDay.GetValueOrDefault( MaxMovableDay );

                // A bit more complex
                if (start.Date <= endDay.Date)
                    scheduler.Add( RecordingDefinition.Create( this, name, identifier, devices, source, start, duration, endDay, repeat ), exceptions );
            }
        }
예제 #6
0
        public void The_Number_Of_Recordings_Per_Plan_Is_Limited()
        {
            // Create component under test
            var cut = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase ) { FreeTVDevice };

            // Add all plans
            for (int i = 0; i <= RecordingScheduler.MaximumRecordingsInPlan; i++)
            {
                // Create recording
                var plan = RecordingDefinition.Create( false, "test", Guid.NewGuid(), null, SourceMock.Source1Group1Free, TimeBias.AddHours( i ), TimeSpan.FromMinutes( 90 ) );

                // Add it
                cut.Add( plan );
            }

            // Resolve
            var schedules = cut.GetSchedules( TimeBias ).ToArray();

            // Validate
            Assert.AreEqual( RecordingScheduler.MaximumRecordingsInPlan + 1, (uint) schedules.Length, "Schedules" );

            // Check all
            for (int i = 0; i < RecordingScheduler.MaximumRecordingsInPlan; i++)
            {
                // Load
                var schedule = schedules[i];

                // Validate
                Assert.AreSame( FreeTVDevice, schedule.Resource, "Resource {0}", i );
                Assert.AreEqual( TimeBias.AddHours( i ), schedule.Time.Start, "Start {0}", i );
                Assert.AreEqual( TimeSpan.FromMinutes( 90 ), schedule.Time.Duration, "Duration {0}", i );
                Assert.IsFalse( schedule.StartsLate, "Late {0}", i );
            }

            // Load the last
            var last = schedules[RecordingScheduler.MaximumRecordingsInPlan];

            // Validate - internal planning is not touched
            Assert.AreSame( FreeTVDevice, last.Resource, "Resource" );
            Assert.AreEqual( TimeBias.AddHours( RecordingScheduler.MaximumRecordingsInPlan ), last.Time.Start, "Start" );
            Assert.AreEqual( TimeSpan.FromMinutes( 90 ), last.Time.Duration, "Duration" );
            Assert.IsFalse( last.StartsLate, "Late" );
        }
예제 #7
0
        public void Performance_Of_A_Real_Life_Scenario()
        {
            // Get the path to the test directories
            var jobDirectory = @"*TBD*";
            var profileDirectory = Environment.ExpandEnvironmentVariables( @"%AllUsersProfile%\DVBNETProfiles" );

            // Helper - will be reused
            var job = new XmlDocument();

            // Sources
            var sourceMap = new Dictionary<string, SourceMock>( StringComparer.InvariantCultureIgnoreCase );
            var profileMap = new Dictionary<string, XmlDocument>( StringComparer.InvariantCultureIgnoreCase );
            var groupMap = new Dictionary<object, Guid>();
            var plan = new List<IRecordingDefinition>();

            // Process all job files
            foreach (var jobPath in Directory.EnumerateFiles( jobDirectory, "*.j39" ))
            {
                // Load to document
                job.Load( jobPath );

                // Common data
                var autoSelect = bool.Parse( job.SelectSingleNode( "VCRJob/AutomaticResourceSelection" ).InnerText );
                var defaultSource = job.SelectSingleNode( "VCRJob/Source" );
                var profileName = defaultSource.InnerText.Substring( defaultSource.InnerText.LastIndexOf( '@' ) + 1 );
                var jobName = job.SelectSingleNode( "VCRJob/Name" ).InnerText;

                // Load the profile once
                XmlDocument profile;
                if (!profileMap.TryGetValue( profileName, out profile ))
                {
                    // Create
                    profile = new XmlDocument();

                    // Load
                    profile.Load( Path.Combine( profileDirectory, profileName + ".dnp" ) );

                    // Remapping
                    for (; ; )
                    {
                        // Create a new namespace table
                        var namespaces = new XmlNamespaceManager( profile.NameTable );

                        // Add us
                        namespaces.AddNamespace( "dvbnet", "http://psimarron.net/DVBNET/Profiles" );

                        // Check for remap
                        var loadFrom = profile.SelectSingleNode( "//dvbnet:UseSourcesFrom", namespaces );
                        if (loadFrom == null)
                            break;

                        // Load the name
                        var refProfileName = loadFrom.InnerText;
                        if (string.IsNullOrEmpty( refProfileName ))
                            break;

                        // Reload
                        if (profileMap.TryGetValue( refProfileName, out profile ))
                            continue;

                        // Create
                        profile = new XmlDocument();

                        // Load
                        profile.Load( Path.Combine( profileDirectory, refProfileName + ".dnp" ) );

                        // Remember
                        profileMap.Add( refProfileName, profile );
                    }

                    // Remember
                    profileMap.Add( profileName, profile );
                }

                // Create a new namespace table
                var profileNamespaces = new XmlNamespaceManager( profile.NameTable );

                // Add us
                profileNamespaces.AddNamespace( "dvbnet", "http://psimarron.net/DVBNET/Profiles" );

                // Validate
                Assert.IsTrue( autoSelect, "Auto Select" );

                // Process all schedules
                foreach (XmlElement schedule in job.SelectNodes( "VCRJob/Schedule" ))
                {
                    // Extract data
                    var repeat = schedule.SelectSingleNode( "Days" ).InnerText.Split( ' ' ).Where( d => !string.IsNullOrEmpty( d ) ).Select( d => (DayOfWeek) Enum.Parse( typeof( DayOfWeek ), d ) ).ToArray();
                    var firstStart = DateTime.Parse( schedule.SelectSingleNode( "FirstStart" ).InnerText, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind );
                    var lastDay = DateTime.Parse( schedule.SelectSingleNode( "LastDay" ).InnerText, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind );
                    var duration = TimeSpan.FromMinutes( uint.Parse( schedule.SelectSingleNode( "Duration" ).InnerText ) );
                    var source = (XmlElement) (schedule.SelectSingleNode( "Source" ) ?? defaultSource);
                    var id = Guid.Parse( schedule.SelectSingleNode( "UniqueID" ).InnerText );
                    var scheduleName = schedule.SelectSingleNode( "Name" ).InnerText;
                    var sourceName = source.GetAttribute( "name" );

                    // Find the real source
                    SourceMock realSource;
                    if (!sourceMap.TryGetValue( sourceName, out realSource ))
                    {
                        // Split name
                        var split = sourceName.LastIndexOf( '[' );
                        var stationName = sourceName.Substring( 0, split ).TrimEnd();
                        var providerName = sourceName.Substring( split + 1 ).TrimEnd( ']' );

                        // Locate the station
                        var station = (XmlElement) profile.SelectSingleNode( "//dvbnet:Station[@name='" + stationName + "' and @provider='" + providerName + "']", profileNamespaces );
                        var encrypted = bool.Parse( station.GetAttribute( "scrambled" ) );
                        var group = station.ParentNode;

                        // Unique group identifier
                        Guid groupIdentifier;
                        if (!groupMap.TryGetValue( group, out groupIdentifier ))
                            groupMap.Add( group, groupIdentifier = Guid.NewGuid() );

                        // Create the source entry
                        sourceMap.Add( sourceName, realSource = SourceMock.Create( sourceName, groupIdentifier, encrypted ) );
                    }

                    // Get the full name
                    if (string.IsNullOrEmpty( scheduleName ))
                        scheduleName = jobName;
                    else if (!string.IsNullOrEmpty( jobName ))
                        scheduleName = jobName + " (" + scheduleName + ")";

                    // Create the request
                    if (repeat.Length > 0)
                        plan.Add( RecordingDefinition.Create( schedule, scheduleName, id, null, realSource, firstStart, duration, lastDay, repeat ) );
                    else
                        plan.Add( RecordingDefinition.Create( schedule, scheduleName, id, null, realSource, firstStart, duration ) );
                }
            }

            // Create component under test
            var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase );
            var sources = sourceMap.Values.ToArray();

            // Add resources - hard coded, should be taken from configuration in some future version
            for (var i = 0; i++ < 4; )
                componentUnderTest.Add( ResourceMock.Create( "duo" + i.ToString( "0" ), sources ).SetEncryptionLimit( 1 ) );

            // Add the plan
            foreach (var recording in plan)
                componentUnderTest.Add( recording );

            // Process
            var refTime = new DateTime( 2013, 10, 26, 12, 0, 0, DateTimeKind.Utc );
            var timer = Stopwatch.StartNew();
            var all = componentUnderTest.GetSchedules( refTime ).Take( 1000 ).ToArray();
            var elapsed = timer.Elapsed;

            // Report
            Console.WriteLine( "{0:N3}ms", elapsed.TotalMilliseconds );

            // Validate
            Assert.AreEqual( 1000, all.Length, "#jobs" );
        }
예제 #8
0
        public void Can_Not_Add_A_Null_Plan_Item()
        {
            // Create component under test
            var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase );

            // Add
            componentUnderTest.Add( default( IRecordingDefinition ) );
        }
예제 #9
0
        public void Can_Handle_Very_Long_Recordings_With_Multiple_Devices()
        {
            // All sources
            var sources =
                Enumerable
                    .Range( 0, 100 )
                    .Select( i => SourceMock.Create( "S" + i.ToString( "00" ) ) )
                    .ToArray();

            // Create environment
            var device1 = ResourceMock.Create( "D1", sources );
            var device2 = ResourceMock.Create( "D2", sources );
            var device3 = ResourceMock.Create( "D3", sources );
            var device4 = ResourceMock.Create( "D4", sources );

            // Create the plan
            var allDays = new[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday, DayOfWeek.Sunday };
            var refTime = new DateTime( 2013, 12, 1 );
            var plan1 = RecordingDefinition.Create( false, "test1", Guid.NewGuid(), null, sources[0], refTime, TimeSpan.FromHours( 23 ), refTime.AddYears( 100 ), allDays );
            var plan2 = RecordingDefinition.Create( false, "test2", Guid.NewGuid(), null, sources[1], refTime.AddHours( 22 ), TimeSpan.FromHours( 4 ), refTime.AddYears( 100 ), allDays );

            // Create the component under test
            var cut = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase ) { device1, device2, device3, device4, plan1, plan2 };

            // Other
            foreach (var plan in
                Enumerable
                    .Range( 2, 10 )
                    .Select( i => RecordingDefinition.Create( false, "test" + i.ToString( "00" ), Guid.NewGuid(), null, sources[i], refTime.AddMinutes( 5 * i ), TimeSpan.FromMinutes( 6 ), refTime.AddYears( 100 ), allDays ) ))
                cut.Add( plan );

            // Get the schedule
            var schedules = cut.GetSchedules( refTime.AddHours( -1 ) ).Take( 500 ).ToArray();

            // Validate
            Assert.AreEqual( 500, schedules.Length, "#count" );
            Assert.IsFalse( schedules.Any( schedule => schedule.StartsLate ), "late!" );

            // Dump plan
            foreach (var schedule in schedules)
                Console.WriteLine( schedule );
        }
예제 #10
0
        public void There_Can_Be_Only_One_Exception_Per_Date()
        {
            // Create bad exception
            var exception1 = new PlanException { ExceptionDate = DateTime.Now.Date };
            var exception2 = new PlanException { ExceptionDate = exception1.ExceptionDate };

            // Create component under test
            var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase );

            // Add
            componentUnderTest.Add( ResourceMock.Create( "r1", SourceMock.Create( "s1" ) ) );
            componentUnderTest.Add( RecordingDefinition.Create( false, "test", Guid.NewGuid(), null, SourceMock.Create( "s1" ), DateTime.UtcNow, TimeSpan.FromMinutes( 12 ) ), exception1, exception2 );
        }
예제 #11
0
        public void Exceptions_Must_Be_Defined_On_A_Full_Date()
        {
            // Create bad exception
            var exception = new PlanException { ExceptionDate = DateTime.Now.Date.AddMinutes( 12 ) };

            // Create component under test
            var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase );

            // Add
            componentUnderTest.Add( ResourceMock.Create( "r1", SourceMock.Create( "s1" ) ) );
            componentUnderTest.Add( RecordingDefinition.Create( false, "test", Guid.NewGuid(), null, SourceMock.Create( "s1" ), DateTime.UtcNow, TimeSpan.FromMinutes( 12 ) ), exception );
        }
예제 #12
0
        public void Encrypted_Source_Of_A_Plan_Item_Is_Supported_By_Resource()
        {
            // Create component under test
            var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase );

            // Add
            componentUnderTest.Add( ResourceMock.Create( "r1", SourceMock.Create( "s1" ) ).SetEncryptionLimit( 1 ) );
            componentUnderTest.Add( RecordingDefinition.Create( false, "test", Guid.NewGuid(), null, SourceMock.Create( "s1", true ), DateTime.UtcNow, TimeSpan.FromMinutes( 12 ) ) );
        }