/// <summary> /// Erzeugt eine neue Planung. /// </summary> /// <param name="resource">Das zugehörige Gerät.</param> /// <param name="schedulePlan">Die zugehörige Gesamtplanung.</param> /// <param name="decryptionCounter">Die Zähler für die Entschlüsselung.</param> /// <param name="allocations">Optional alle bereits vorgenommenen Zuordnungen.</param> /// <param name="planTime">Der aktuelle Planungsbeginn, sofern bekannt.</param> /// <exception cref="ArgumentNullException">Es wurde kein Gerät angegeben.</exception> public ResourcePlan(IScheduleResource resource, SchedulePlan schedulePlan, HashSet <Guid> decryptionCounter = null, AllocationMap allocations = null, DateTime?planTime = null) { // Remember SchedulePlan = schedulePlan; Resource = resource; // Register a single decryption counter DecryptionCounters = decryptionCounter ?? new HashSet <Guid> { schedulePlan.RegisterDecryption(Resource.Decryption.MaximumParallelSources) }; // Check for allocation if (allocations != null) { // Just clone Allocations = allocations.Clone(planTime); } else { // Number of sources we may use var sourceLimit = resource.SourceLimit; if (sourceLimit < 1) { sourceLimit = int.MaxValue; } // Create brand new Allocations = new AllocationMap(sourceLimit, CheckForSourceGroupMatch); } }
/// <summary> /// Beendet eine Aufzeichnung auf einem Gerät. /// </summary> /// <param name="scheduleIdentifier">Die eindeutige Kennung der Aufzeichnung.</param> public void Stop(Guid scheduleIdentifier) { // Locate and validate var index = this.FindIndex(scheduleIdentifier); // Keep current recording settings var recording = m_Recordings[index]; // Remove it m_Recordings.RemoveAt(index); // Create a new plan try { // Get the plan - should NEVER fail or we are in BIG trouble var plan = CreateSchedulePlan(); if (plan == null) { throw new InvalidOperationException("Stop"); } // Remember the new situation m_CurrentPlan = plan; } catch { // Recover m_Recordings.Insert(index, recording); // Forward throw; } }
/// <summary> /// Ermittelt den besten Ausführungsplan. /// </summary> /// <param name="plans">Eine Liste von Plänen.</param> /// <param name="comparer">Der Algorithmus, der entscheidet, wann ein Plan besser als ein anderer ist.</param> /// <returns>Der beste Plan.</returns> /// <exception cref="ArgumentNullException">Es wurde keine Liste angegeben.</exception> public static SchedulePlan FindBest(IEnumerable <SchedulePlan> plans, IComparer <SchedulePlan> comparer) { // Validate if (plans == null) { throw new ArgumentNullException("plans"); } #if !SILVERLIGHT // Report if (RecordingScheduler.SchedulerTrace.TraceInfo) { Trace.TraceInformation(Properties.SchedulerResources.Trace_ChooseBestPlan, plans.Count()); } #endif // Reset SchedulePlan best = null; // Process all foreach (var plan in plans) { if (plan.CompareTo(best, comparer) > 0) { best = plan; } } // Report return(best); }
/// <summary> /// Erzeugt eine Kopie dieser Planung. /// </summary> /// <param name="original">Die originalen Planungsdaten.</param> /// <param name="schedulePlan">Die zugehörige Gesamtplanung.</param> private ResourcePlan(ResourcePlan original, SchedulePlan schedulePlan) : this(original.Resource, schedulePlan, original.DecryptionCounters, original.Allocations) { // Finish clone process m_Recordings.AddRange(original.m_Recordings); CutRecordings = original.CutRecordings; TotalCut = original.TotalCut; }
/// <summary> /// Aktiviert eine Aufzeichnung auf einem Gerät. /// </summary> /// <param name="resource">Eines der verwalteten Geräte.</param> /// <param name="source">Optional eine Quelle, die auf dem Gerät angesteuert werden kann.</param> /// <param name="scheduleIdentifier">Die eindeutige Kennung der Aufzeichnung.</param> /// <param name="scheduleName">Der Anzeigename der Aufzeichnung.</param> /// <param name="plannedStart">Der ursprüngliche Start der Aufzeichnung in UTC / GMT Notation.</param> /// <param name="currentEnd">Das aktuelle Ende der Aufzeichnung in UTC / GMT Notation.</param> /// <returns>Gesetzt, wenn die Aufzeichnung auf dem gewünschten Gerät aktiviert werden kann.</returns> /// <exception cref="ArgumentNullException">Es wurde kein Gerät angegeben.</exception> /// <exception cref="ArgumentException">Das Gerät ist nicht bekannt oder kann die Quelle nicht empfangen.</exception> /// <exception cref="ArgumentOutOfRangeException">Die Laufzeit der Aufzeichnung ist nicht positiv.</exception> public bool Start(IScheduleResource resource, IScheduleSource source, Guid scheduleIdentifier, string scheduleName, DateTime plannedStart, DateTime currentEnd) { // Validate if (resource == null) { throw new ArgumentNullException("resource"); } if (!m_Resources.Contains(resource)) { throw new ArgumentException(resource.Name, "resource"); } if (plannedStart >= currentEnd) { throw new ArgumentOutOfRangeException("currentEnd"); } if (m_Recordings.Any(r => r.UniqueIdentifier == scheduleIdentifier)) { throw new ArgumentException("resource"); } // Create helper entry m_Recordings.Add(new ResourceAllocationInformation(resource, source, scheduleIdentifier, scheduleName, plannedStart, currentEnd - plannedStart)); // May require cleanup try { // Create the new plan var plan = CreateSchedulePlan(); // Did it if (plan != null) { // Remember as current m_CurrentPlan = plan; // Report return(true); } } catch { // Cleanup m_Recordings.RemoveAt(m_Recordings.Count - 1); // Report throw; } // Cleanup m_Recordings.RemoveAt(m_Recordings.Count - 1); // Add to list return(false); }
/// <summary> /// Erzeugt einen neuen Plan basierend auf der aktuellen Zuordnung. /// </summary> /// <param name="planTime">Der aktuelle Planungsbeginn, sofern bekannt.</param> /// <returns>Ein neuer Plan.</returns> public SchedulePlan Restart(DateTime?planTime) { // Create - make sure that we keep the decryption allocations var clone = new SchedulePlan(ResourceCollection, DecryptionCounters, planTime); // Fill clone.Resources = Resources.Select(r => r.Restart(clone, planTime)).ToArray(); // Report return(clone); }
/// <summary> /// Erstellt eine exakte unabhängige Kopie die Planung. /// </summary> /// <param name="original">Die ursprüngliche Planung.</param> private SchedulePlan(SchedulePlan original) : this(original.ResourceCollection, original.DecryptionCounters, null) { // Create array Resources = new ResourcePlan[original.Resources.Length]; // Deep clone for (int i = Resources.Length; i-- > 0;) { Resources[i] = original.Resources[i].Clone(this); } }
/// <summary> /// Gibt einen Plan aus. /// </summary> /// <param name="plan">Der zu verwendende Plan.</param> /// <returns>Alle Aufzeichnungen, geordnet erst nach Zeit und dann nach der Priorität des Gerätes.</returns> private IEnumerable <ScheduleInfo> Dump(SchedulePlan plan) { // Skip if (plan == null) { yield break; } #if !SILVERLIGHT // Dump decryption allocation if (SchedulerTrace.TraceVerbose) { foreach (var decryption in plan.DecryptionCounters.Values) { if (decryption.IsEnabled) { Trace.TraceInformation(Properties.SchedulerResources.Trace_Decryption, decryption); } } } #endif // Artifical dump foreach (var info in plan.GetRecordings()) { if (!m_ForbiddenDefinitions.Contains(info.Definition.UniqueIdentifier)) { #if !SILVERLIGHT // Trace if (SchedulerTrace.TraceInfo) { // Check type var definition = info.Definition as IRecordingDefinition; // Report Trace.TraceInformation ( info.StartsLate ? Properties.SchedulerResources.Trace_LateItem : Properties.SchedulerResources.Trace_NormalItem, info.Resource, (definition == null) ? null : definition.Source, info.Time.Start, info.Time.Duration ); } #endif // Report yield return(info); } } }
/// <summary> /// Erzeugt eine Planungsinstanz basierend auf den aktuell bekannten Aufzeichnungen. /// </summary> /// <returns>Der gewünschte Plan.</returns> private SchedulePlan CreateSchedulePlan() { // Create var plan = new SchedulePlan(m_Resources); // Report var resources = plan.Resources.ToDictionary(r => r.Resource, ReferenceComparer <IScheduleResource> .Default); // Merge in each schedule foreach (var recording in m_Recordings) { // Attach to the resource var resource = recording.Resource; var resourcePlan = resources[resource]; // Check mode if (recording.Source == null) { // Try to reserve if (!resourcePlan.Reserve(recording.Time.End)) { return(null); } // Next continue; } // Attach to the timing SuggestedPlannedTime planned = recording.Time; // Try add if (!resourcePlan.Add(recording, planned, DateTime.MinValue)) { return(null); } // Must not start late if (planned.Planned.Start != recording.Time.Start) { return(null); } } // Report return(plan); }
/// <summary> /// Verändert eine Aufzeichnung. /// </summary> /// <param name="scheduleIdentifier">Die eindeutige Kennung der Aufzeichnung.</param> /// <param name="newEnd">Der neue Endzeitpunkt der Aufzeichnung.</param> /// <returns>Gesetzt, wenn die Veränderung möglich ist.</returns> public bool Modify(Guid scheduleIdentifier, DateTime newEnd) { // Locate and validate var index = this.FindIndex(scheduleIdentifier); // Keep current recording settings var recording = m_Recordings[index]; // Validate if (newEnd <= recording.Time.Start) { throw new ArgumentOutOfRangeException("newEnd"); } // Remove it m_Recordings[index] = new ResourceAllocationInformation(recording.Resource, recording.Source, recording.UniqueIdentifier, recording.Name, recording.Time.Start, newEnd - recording.Time.Start); // Create a new plan try { // Try to apply the change var plan = CreateSchedulePlan(); if (plan != null) { // Remember the new situation m_CurrentPlan = plan; // Confirm return(true); } } catch { // Recover m_Recordings[index] = recording; // Forward throw; } // Recover m_Recordings[index] = recording; // Back to normal return(false); }
/// <summary> /// Erzeugt eine passende Planungskomponente. /// </summary> /// <param name="excludeActiveRecordings">Gesetzt um alle bereits aktiven Aufzeichnungen auszublenden.</param> /// <returns>Die zur aktuellen Reservierung passende Planungskomponente.</returns> public RecordingScheduler CreateScheduler(bool excludeActiveRecordings = true) { // Time to create plan if (m_CurrentPlan == null) { if ((m_CurrentPlan = CreateSchedulePlan()) == null) { throw new NotSupportedException("CreateScheduler"); } } // Create exclusion map var alreadyActive = new HashSet <Guid>(excludeActiveRecordings ? m_Recordings.Select(r => r.UniqueIdentifier) : Enumerable.Empty <Guid>()); // Process return(new RecordingScheduler(m_Resources, alreadyActive, () => m_CurrentPlan, m_PlanComparer)); }
/// <summary> /// Vergleicht zwei Pläne. Ein Plan ist umso besser, je mehr Aufzeichnung auf einem hoch priorisierten /// Gerät ausgeführt werden. Relevant ist die Reihenfolge der Geräte in der Liste. /// </summary> /// <param name="other">Ein anderer Plan.</param> /// <param name="comparer">Die Vergleichsregeln.</param> /// <returns>Der Unterschied zwischen den Plänen.</returns> public int CompareTo(SchedulePlan other, IComparer <SchedulePlan> comparer) { // Validate if (comparer == null) { throw new ArgumentNullException("comparer"); } // Not possible if (other == null) { return(+1); } // Internal cross check if (Resources.Length != other.Resources.Length) { throw new InvalidOperationException("Resources.Length"); } // Process return(comparer.Compare(this, other)); }
/// <summary> /// Erstellt einen neuen Vergleichsalgorithmus auf Basis einer Dateibeschreibung. /// </summary> /// <param name="fileContents">Der tatsächliche Inhalt der Datei.</param> /// <param name="nameComparer">Der Algorithmus zum Vegleich von Gerätenamen.</param> /// <returns>Die gewünschte Beschreibung.</returns> internal static IComparer <SchedulePlan> Create(byte[] fileContents, IEqualityComparer <string> nameComparer) { // Validate if (fileContents == null) { throw new ArgumentNullException("fileContents"); } // Result var resourceComparer = default(CompoundComparer <ResourcePlan>); var planComparer = new CompoundComparer <SchedulePlan>(); // Create reader using (var stream = new MemoryStream(fileContents, false)) using (var reader = new StreamReader(stream, true)) for (string line; (line = reader.ReadLine()) != null;) { // Check for comment and empty line var dataLength = line.IndexOf('#'); var data = line.Substring(0, (dataLength < 0) ? line.Length : dataLength).Trim(); if (string.IsNullOrEmpty(data)) { continue; } // Check operation var parts = data.Split(':'); if (parts.Length != 2) { throw new InvalidDataException(string.Format(Properties.SchedulerResources.Exception_BadScheduleFile, data)); } // Check mode if (resourceComparer == null) { // New rule to use var rule = default(IComparer <SchedulePlan>); // Check for supported operations switch (parts[0]) { case "StartTime": planComparer.Comparers.Add(SchedulePlan.CompareByOverlappingStart(parts[1], nameComparer)); continue; case "ParallelSource": rule = SchedulePlan.CompareByParallelSourceTime; break; case "ResourceCount": rule = SchedulePlan.CompareByResourceCount; break; case "TotalCut": rule = SchedulePlan.CompareByTotalCut; break; case "ByPriority": { // Create inner resourceComparer = new CompoundComparer <ResourcePlan>(); // Check mode switch (parts[1]) { case "Ascending": rule = resourceComparer.ByPriority(false); break; case "Descending": rule = resourceComparer.ByPriority(true); break; default: throw new InvalidDataException(string.Format(Properties.SchedulerResources.Exception_UnknownOrder, parts[0], parts[1])); } // Done break; } } // Validate if (rule == null) { throw new InvalidDataException(string.Format(Properties.SchedulerResources.Exception_UnknownProperty, parts[0])); } // Process adaption if (resourceComparer == null) { switch (parts[1]) { case "Min": rule = Invert(rule); break; case "Max": break; default: throw new InvalidDataException(string.Format(Properties.SchedulerResources.Exception_UnknownOrder, parts[0], parts[1])); } } // Remember planComparer.Comparers.Add(rule); } else { // New rule to use var rule = default(IComparer <ResourcePlan>); // Check for supported operations switch (parts[0]) { case "RecordingCount": rule = ResourcePlan.CompareByRecordingCount; break; case "SourceCount": rule = ResourcePlan.CompareBySourceCount; break; case "ByPriority": { // Check mode switch (parts[1]) { case "End": resourceComparer = null; break; default: throw new InvalidDataException(Properties.SchedulerResources.Exception_ResourcesNotTerminated); } // Done break; } } // Validate if (rule != null) { // May invert switch (parts[1]) { case "Min": rule = Invert(rule); break; case "Max": break; default: throw new InvalidDataException(string.Format(Properties.SchedulerResources.Exception_UnknownOrder, parts[0], parts[1])); } // Remember resourceComparer.Comparers.Add(rule); } else if (resourceComparer != null) { throw new InvalidDataException(string.Format(Properties.SchedulerResources.Exception_UnknownProperty, parts[0])); } } } // Validate if (resourceComparer != null) { throw new InvalidDataException(Properties.SchedulerResources.Exception_ResourcesNotTerminated); } // Report return(planComparer); }
/// <summary> /// Meldet alle Aufzeichnungen ab einem bestimmten Zeitpunkt. /// </summary> /// <param name="minTime">Alle Aufzeichnungen, die vor diesem Zeitpunkt enden, werden /// nicht berücksichtigt. Die Angabe erfolgt in UTC / GMT Notation.</param> /// <returns>Alle Aufzeichnungen.</returns> private IEnumerable <ScheduleInfo> GetSchedulesForRecordings(DateTime minTime) { // All items to process var items = new _ScheduleList(m_PlanItems.Where(r => !m_ForbiddenDefinitions.Contains(r.Definition.UniqueIdentifier)), minTime); // Create the plans to extend var plans = new List <SchedulePlan> { m_PlanCreator() }; var steps = 0; // As long as necessary while (items.MoveNext()) { // Load the item var candidate = items.Current; var candiateTime = candidate.Current; var planned = candiateTime.Planned; #if !SILVERLIGHT // Report if (SchedulerTrace.TraceVerbose) { Trace.TraceInformation(Properties.SchedulerResources.Trace_Candidate, candidate.Definition.Source, planned.Start, planned.Duration); } #endif // Get the current end of plans and see if we can dump the state - this may increase performance var planStart = plans.SelectMany(p => p.Resources).Min(r => (DateTime?)r.PlanStart); var planEnd = plans.SelectMany(p => p.Resources).Max(r => (DateTime?)r.PlanEnd); var canEndPlan = planEnd.HasValue && (planEnd.Value != DateTime.MinValue) && (planned.Start >= planEnd.Value); var mustEndPlan = planStart.HasValue && (planStart.Value != DateTime.MaxValue) && planEnd.HasValue && (planEnd.Value != DateTime.MinValue) && ((planEnd.Value - planStart.Value).TotalDays > 2); // Count this effort if ((++steps > MaximumRecordingsInPlan) || canEndPlan || mustEndPlan || (plans.Count > MaximumAlternativesInPlan)) { // Find best plan var best = SchedulePlan.FindBest(plans, m_comparer); // Report foreach (var info in Dump(best)) { yield return(info); } // Reset plans.Clear(); plans.Add(best.Restart(planned.Start)); // Reset steps = 1; } #if !SILVERLIGHT // Report if (SchedulerTrace.TraceVerbose) { Trace.TraceInformation(Properties.SchedulerResources.Trace_PlanCount, plans.Count); } #endif // All plans to extend var allPlans = plans.ToArray(); // Discard list plans.Clear(); // Iterate over all plans and try to add the current candidate - in worst case this will multiply possible plans by the number of resources available foreach (var plan in allPlans) { for (int i = plan.Resources.Length; i-- > 0;) { // Clone of plan must be recreated for each resource because test is allowed to modify it var clone = plan.Clone(); // Remember the time we tried - implicit cast is important, do NOT use var SuggestedPlannedTime plannedTime = planned; // See if resource can handle this if (clone.Resources[i].Add(candidate.Definition, plannedTime, minTime)) { plans.Add(clone); } } } // Must reset if the recording could not be scheduled at all if (plans.Count < 1) { // Report yield return(new ScheduleInfo(candidate.Definition, null, planned, false)); // Restore the original plans since we did nothing at all plans.AddRange(allPlans); } } // Send all we found foreach (var info in Dump(SchedulePlan.FindBest(plans, m_comparer))) { yield return(info); } }
/// <summary> /// Erzeugt eine neue Instanz unter Berücksichtigung der vorgenommenen Gerätezuordnung. /// </summary> /// <param name="plan">Ein neuer Plan.</param> /// <param name="planTime">Der aktuelle Planungsbeginn, sofern bekannt.</param> /// <returns>Eine Beschreibung des Gerätes.</returns> public ResourcePlan Restart(SchedulePlan plan, DateTime?planTime) { // Create new - this will reset the allocation map to improve performance return(new ResourcePlan(Resource, plan, DecryptionCounters, Allocations, planTime)); }
/// <summary> /// Erzeugt eine exakte Kopie dieser Planung. /// </summary> /// <param name="schedulePlan">Die zugehörige Gesamtplanung.</param> /// <returns>Die gewünschte Kopie.</returns> public ResourcePlan Clone(SchedulePlan schedulePlan) { // Forward return(new ResourcePlan(this, schedulePlan)); }
/// <summary> /// Meldet, ob eine Planung unsere Regel verletzt. /// </summary> /// <param name="plan">Der zu prüfende Plan.</param> /// <returns>Zählt, wie oft ein Starte des problematischen Gerätes erfolgt.</returns> public int Count(SchedulePlan plan) { // Find the primary resource var primary = plan.Resources.FirstOrDefault(r => m_nameComparer.Equals(r.Resource.Name, m_leadingResource)); if (primary == null) { return(0); } // All times where we really started the resource var startTimes = primary.Allocations.ResourceStartTimes; if (startTimes.Length < 1) { return(0); } // Counter var badCount = 0; // Now inspect all resources of interest foreach (var resource in plan.Resources) { // Our primary var name = resource.Resource.Name; if (m_nameComparer.Equals(name, m_leadingResource)) { continue; } // Not of interest if (m_testResources != null) { if (!m_testResources.Contains(name)) { continue; } } // Is idle var allocations = resource.Allocations; if (allocations.IsEmpty) { continue; } // Current index of inspection var startTime = startTimes[0]; var startIndex = 0; // Process all allocations foreach (var allocation in allocations) { // Doing nothing is no problem if (allocation.IsIdle) { continue; } // As long as necessary for (; ;) { // Too early if (allocation.End <= startTime) { break; } // Rule violated if (allocation.Start <= startTime) { badCount++; } // Adjust to next if (++startIndex >= startTimes.Length) { break; } // Reload for next try startTime = startTimes[startIndex]; } // We already processed all start times - speed up at least a tiny bit if (startIndex >= startTimes.Length) { break; } } } // Report the total violations - overall: the less the better return(badCount); }