Files
FSI.BT.IR.Tools/RoboSharp/Results/RoboCopyResultsList.cs
Maier Stephan SI 1c68b8f401 Sicherung
2023-04-17 07:07:49 +02:00

397 lines
18 KiB
C#

using System;
using System.Collections.Generic;
//using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using RoboSharp.EventArgObjects;
using RoboSharp.Interfaces;
using StatType = RoboSharp.Results.Statistic.StatType;
using System.Collections;
namespace RoboSharp.Results
{
/// <summary>
/// Object used to represent results from multiple <see cref="RoboCommand"/>s. <br/>
/// As <see cref="RoboCopyResults"/> are added to this object, it will update the Totals and Averages accordingly.<para/>
/// Implements:
/// <br/><see cref="IRoboCopyResultsList"/>
/// <br/><see cref="IList{RoboCopyResults}"/> where T = RoboCopyResults
/// <br/><see cref="INotifyCollectionChanged"/>
/// </summary>
/// <remarks>
/// <see href="https://github.com/tjscience/RoboSharp/wiki/RoboCopyResultsList"/>
/// </remarks>
public sealed class RoboCopyResultsList : IRoboCopyResultsList, IList<RoboCopyResults>, INotifyCollectionChanged
{
#region < Constructors >
/// <inheritdoc cref="List{T}.List()"/>
public RoboCopyResultsList() { InitCollection(null); Init(); }
/// <param name="result">Populate the new List object with this result as the first item.</param>
/// <inheritdoc cref="List{T}.List(IEnumerable{T})"/>
public RoboCopyResultsList(RoboCopyResults result) { ResultsList.Add(result); InitCollection(null); Init(); }
/// <inheritdoc cref="List{T}.List(IEnumerable{T})"/>
public RoboCopyResultsList(IEnumerable<RoboCopyResults> collection) { InitCollection(collection); Init(); }
/// <summary>
/// Clone a RoboCopyResultsList into a new object
/// </summary>
public RoboCopyResultsList(RoboCopyResultsList resultsList)
{
InitCollection(resultsList);
Total_DirStatsField = GetLazyStat(resultsList.Total_DirStatsField, GetLazyFunc(GetDirectoriesStatistics, StatType.Directories));
Total_FileStatsField = GetLazyStat(resultsList.Total_FileStatsField, GetLazyFunc(GetFilesStatistics, StatType.Files));
Total_ByteStatsField = GetLazyStat(resultsList.Total_ByteStatsField, GetLazyFunc(GetByteStatistics, StatType.Bytes));
Average_SpeedStatsField = GetLazyStat(resultsList.Average_SpeedStatsField, GetLazyAverageSpeedFunc());
ExitStatusSummaryField = GetLazyStat(resultsList.ExitStatusSummaryField, GetLazCombinedStatusFunc());
}
#region < Constructor Helper Methods >
private void InitCollection(IEnumerable<RoboCopyResults> collection)
{
ResultsList.AddRange(collection);
ResultsList.CollectionChanged += OnCollectionChanged;
}
private void Init()
{
Total_DirStatsField = new Lazy<Statistic>(GetLazyFunc(GetDirectoriesStatistics, StatType.Directories));
Total_FileStatsField = new Lazy<Statistic>(GetLazyFunc(GetFilesStatistics, StatType.Files));
Total_ByteStatsField = new Lazy<Statistic>(GetLazyFunc(GetByteStatistics, StatType.Bytes));
Average_SpeedStatsField = new Lazy<AverageSpeedStatistic>(GetLazyAverageSpeedFunc());
ExitStatusSummaryField = new Lazy<RoboCopyCombinedExitStatus>(GetLazCombinedStatusFunc());
}
private Func<Statistic> GetLazyFunc(Func<IStatistic[]> Action, StatType StatType) => new Func<Statistic>(() => Statistic.AddStatistics(Action.Invoke(), StatType));
private Func<AverageSpeedStatistic> GetLazyAverageSpeedFunc() => new Func<AverageSpeedStatistic>(() => AverageSpeedStatistic.GetAverage(GetSpeedStatistics()));
private Func<RoboCopyCombinedExitStatus> GetLazCombinedStatusFunc() => new Func<RoboCopyCombinedExitStatus>(() => RoboCopyCombinedExitStatus.CombineStatuses(GetStatuses()));
private Lazy<T> GetLazyStat<T>(Lazy<T> lazyStat, Func<T> action) where T : ICloneable
{
if (lazyStat.IsValueCreated)
{
var clone = lazyStat.Value.Clone();
return new Lazy<T>(() => (T)clone);
}
else
{
return new Lazy<T>(action);
}
}
#endregion
#endregion
#region < Fields >
//These objects are the underlying Objects that may be bound to by consumers.
//The values are updated upon request of the associated property.
//This is so that properties are not returning new objects every request (which would break bindings)
//If the statistic is never requested, then Lazy<> allows the list to skip performing the math against that statistic.
private Lazy<Statistic> Total_DirStatsField;
private Lazy<Statistic> Total_ByteStatsField;
private Lazy<Statistic> Total_FileStatsField;
private Lazy<AverageSpeedStatistic> Average_SpeedStatsField;
private Lazy<RoboCopyCombinedExitStatus> ExitStatusSummaryField;
private readonly ObservableList<RoboCopyResults> ResultsList = new ObservableList<RoboCopyResults>();
#endregion
#region < Events >
/// <summary>
/// Delegate for objects to send notification that the list behind an <see cref="IRoboCopyResultsList"/> interface has been updated
/// </summary>
public delegate void ResultsListUpdated(object sender, ResultListUpdatedEventArgs e);
#endregion
#region < Public Properties >
/// <summary> Sum of all DirectoryStatistics objects </summary>
/// <remarks>Underlying value is Lazy{Statistic} object - Initial value not calculated until first request. </remarks>
public IStatistic DirectoriesStatistic => Total_DirStatsField?.Value;
/// <summary> Sum of all ByteStatistics objects </summary>
/// <remarks>Underlying value is Lazy{Statistic} object - Initial value not calculated until first request. </remarks>
public IStatistic BytesStatistic => Total_ByteStatsField?.Value;
/// <summary> Sum of all FileStatistics objects </summary>
/// <remarks>Underlying value is Lazy{Statistic} object - Initial value not calculated until first request. </remarks>
public IStatistic FilesStatistic => Total_FileStatsField?.Value;
/// <summary> Average of all SpeedStatistics objects </summary>
/// <remarks>Underlying value is Lazy{SpeedStatistic} object - Initial value not calculated until first request. </remarks>
public ISpeedStatistic SpeedStatistic => Average_SpeedStatsField?.Value;
/// <summary> Sum of all RoboCopyExitStatus objects </summary>
/// <remarks>Underlying value is Lazy object - Initial value not calculated until first request. </remarks>
public IRoboCopyCombinedExitStatus Status => ExitStatusSummaryField?.Value;
/// <summary> The Collection of RoboCopy Results. Add/Removal of <see cref="RoboCopyResults"/> objects must be performed through this object's methods, not on the list directly. </summary>
public IReadOnlyList<RoboCopyResults> Collection => ResultsList;
/// <inheritdoc cref="List{T}.Count"/>
public int Count => ResultsList.Count;
/// <summary>
/// Get or Set the element at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the item to Get or Set.</param>
/// <exception cref="ArgumentOutOfRangeException"/>
public RoboCopyResults this[int index] { get => ResultsList[index]; set => ResultsList[index] = value; }
#endregion
#region < Get Array Methods ( Public ) >
/// <summary>
/// Get a snapshot of the ByteStatistics objects from this list.
/// </summary>
/// <returns>New array of the ByteStatistic objects</returns>
public IStatistic[] GetByteStatistics()
{
List<Statistic> tmp = new List<Statistic> { };
foreach (RoboCopyResults r in this)
tmp.Add(r?.BytesStatistic);
return tmp.ToArray();
}
/// <summary>
/// Get a snapshot of the DirectoriesStatistic objects from this list.
/// </summary>
/// <returns>New array of the DirectoriesStatistic objects</returns>
public IStatistic[] GetDirectoriesStatistics()
{
List<Statistic> tmp = new List<Statistic> { };
foreach (RoboCopyResults r in this)
tmp.Add(r?.DirectoriesStatistic);
return tmp.ToArray();
}
/// <summary>
/// Get a snapshot of the FilesStatistic objects from this list.
/// </summary>
/// <returns>New array of the FilesStatistic objects</returns>
public IStatistic[] GetFilesStatistics()
{
List<Statistic> tmp = new List<Statistic> { };
foreach (RoboCopyResults r in this)
tmp.Add(r?.FilesStatistic);
return tmp.ToArray();
}
/// <summary>
/// Get a snapshot of the FilesStatistic objects from this list.
/// </summary>
/// <returns>New array of the FilesStatistic objects</returns>
public RoboCopyExitStatus[] GetStatuses()
{
List<RoboCopyExitStatus> tmp = new List<RoboCopyExitStatus> { };
foreach (RoboCopyResults r in this)
tmp.Add(r?.Status);
return tmp.ToArray();
}
/// <summary>
/// Get a snapshot of the FilesStatistic objects from this list.
/// </summary>
/// <returns>New array of the FilesStatistic objects</returns>
public ISpeedStatistic[] GetSpeedStatistics()
{
List<SpeedStatistic> tmp = new List<SpeedStatistic> { };
foreach (RoboCopyResults r in this)
tmp.Add(r?.SpeedStatistic);
return tmp.ToArray();
}
/// <summary>
/// Combine the <see cref="RoboCopyResults.RoboCopyErrors"/> into a single array of errors
/// </summary>
/// <returns>New array of the ErrorEventArgs objects</returns>
public ErrorEventArgs[] GetErrors()
{
List<ErrorEventArgs> tmp = new List<ErrorEventArgs> { };
foreach (RoboCopyResults r in this)
tmp.AddRange(r?.RoboCopyErrors);
return tmp.ToArray();
}
#endregion
#region < INotifyCollectionChanged >
/// <summary>
/// <inheritdoc cref="ObservableList{T}.CollectionChanged"/>
/// </summary>
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <summary>Process the Added/Removed items, then fire the event</summary>
/// <inheritdoc cref="ObservableList{T}.OnCollectionChanged(NotifyCollectionChangedEventArgs)"/>
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Move) goto RaiseEvent; // Sorting causes no change in math -> Simply raise the event
//Reset means a drastic change -> Recalculate everything, then goto RaiseEvent
if (e.Action == NotifyCollectionChangedAction.Reset)
{
//Bytes
if (Total_ByteStatsField.IsValueCreated)
{
Total_ByteStatsField.Value.Reset(false);
Total_ByteStatsField.Value.AddStatistic(GetByteStatistics());
}
//Directories
if (Total_DirStatsField.IsValueCreated)
{
Total_DirStatsField.Value.Reset(false);
Total_DirStatsField.Value.AddStatistic(GetDirectoriesStatistics());
}
//Files
if (Total_FileStatsField.IsValueCreated)
{
Total_FileStatsField.Value.Reset(false);
Total_FileStatsField.Value.AddStatistic(GetFilesStatistics());
}
//Exit Status
if (ExitStatusSummaryField.IsValueCreated)
{
ExitStatusSummaryField.Value.Reset(false);
ExitStatusSummaryField.Value.CombineStatus(GetStatuses());
}
//Speed
if (Average_SpeedStatsField.IsValueCreated)
{
Average_SpeedStatsField.Value.Reset(false);
Average_SpeedStatsField.Value.Average(GetSpeedStatistics());
}
goto RaiseEvent;
}
//Process New Items
if (e.NewItems != null)
{
int i = 0;
int i2 = e.NewItems.Count;
foreach (RoboCopyResults r in e?.NewItems)
{
i++;
bool RaiseValueChangeEvent = (e.OldItems == null || e.OldItems.Count == 0) && (i == i2); //Prevent raising the event if more calculation needs to be performed either from NewItems or from OldItems
//Bytes
if (Total_ByteStatsField.IsValueCreated)
Total_ByteStatsField.Value.AddStatistic(r?.BytesStatistic, RaiseValueChangeEvent);
//Directories
if (Total_DirStatsField.IsValueCreated)
Total_DirStatsField.Value.AddStatistic(r?.DirectoriesStatistic, RaiseValueChangeEvent);
//Files
if (Total_FileStatsField.IsValueCreated)
Total_FileStatsField.Value.AddStatistic(r?.FilesStatistic, RaiseValueChangeEvent);
//Exit Status
if (ExitStatusSummaryField.IsValueCreated)
ExitStatusSummaryField.Value.CombineStatus(r?.Status, RaiseValueChangeEvent);
//Speed
if (Average_SpeedStatsField.IsValueCreated)
{
Average_SpeedStatsField.Value.Add(r?.SpeedStatistic);
if (RaiseValueChangeEvent) Average_SpeedStatsField.Value.CalculateAverage();
}
}
}
//Process Removed Items
if (e.OldItems != null)
{
int i = 0;
int i2 = e.OldItems.Count;
foreach (RoboCopyResults r in e?.OldItems)
{
i++;
bool RaiseValueChangeEvent = i == i2;
//Bytes
if (Total_ByteStatsField.IsValueCreated)
Total_ByteStatsField.Value.Subtract(r?.BytesStatistic, RaiseValueChangeEvent);
//Directories
if (Total_DirStatsField.IsValueCreated)
Total_DirStatsField.Value.Subtract(r?.DirectoriesStatistic, RaiseValueChangeEvent);
//Files
if (Total_FileStatsField.IsValueCreated)
Total_FileStatsField.Value.Subtract(r?.FilesStatistic, RaiseValueChangeEvent);
//Exit Status
if (ExitStatusSummaryField.IsValueCreated && RaiseValueChangeEvent)
{
ExitStatusSummaryField.Value.Reset(false);
ExitStatusSummaryField.Value.CombineStatus(GetStatuses());
}
//Speed
if (Average_SpeedStatsField.IsValueCreated)
{
if (this.Count == 0)
Average_SpeedStatsField.Value.Reset();
else
Average_SpeedStatsField.Value.Subtract(r.SpeedStatistic);
if (RaiseValueChangeEvent) Average_SpeedStatsField.Value.CalculateAverage();
}
}
}
RaiseEvent:
//Raise the CollectionChanged event
CollectionChanged?.Invoke(this, e);
}
#endregion
#region < ICloneable >
/// <summary> Clone this object to a new RoboCopyResultsList </summary>
public RoboCopyResultsList Clone() => new RoboCopyResultsList(this);
#endregion
#region < IList{T} Implementation >
bool ICollection<RoboCopyResults>.IsReadOnly => false;
/// <inheritdoc cref="IList{T}.IndexOf(T)"/>
public int IndexOf(RoboCopyResults item) => ResultsList.IndexOf(item);
/// <inheritdoc cref="ObservableList{T}.Insert(int, T)"/>
public void Insert(int index, RoboCopyResults item) => ResultsList.Insert(index, item);
/// <inheritdoc cref="ObservableList{T}.RemoveAt(int)"/>
public void RemoveAt(int index) => ResultsList.RemoveAt(index);
/// <inheritdoc cref="ObservableList{T}.Add(T)"/>
public void Add(RoboCopyResults item) => ResultsList.Add(item);
/// <inheritdoc cref="ObservableList{T}.Clear"/>
public void Clear() => ResultsList.Clear();
/// <inheritdoc cref="IList.Contains(object)"/>
public bool Contains(RoboCopyResults item) => ResultsList.Contains(item);
/// <inheritdoc cref="ICollection.CopyTo(Array, int)"/>
public void CopyTo(RoboCopyResults[] array, int arrayIndex) => ResultsList.CopyTo(array, arrayIndex);
/// <inheritdoc cref="ObservableList{T}.Remove(T)"/>
public bool Remove(RoboCopyResults item) => ResultsList.Remove(item);
/// <inheritdoc cref="List{T}.GetEnumerator"/>
public IEnumerator<RoboCopyResults> GetEnumerator() => ResultsList.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ResultsList.GetEnumerator();
#endregion
}
}