using System; using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Collections.Generic; using System.ComponentModel; using System.Runtime.CompilerServices; using FSI.Lib.Tools.RoboSharp.Interfaces; namespace FSI.Lib.Tools.RoboSharp.Results { /// /// Contains information regarding average Transfer Speed.
/// Note: Runs that do not perform any copy operations or that exited prematurely ( ) will result in a null object. ///
/// /// /// public class SpeedStatistic : INotifyPropertyChanged, ISpeedStatistic { /// /// Create new SpeedStatistic /// public SpeedStatistic() { } /// /// Clone a SpeedStatistic /// public SpeedStatistic(SpeedStatistic stat) { BytesPerSec = stat.BytesPerSec; MegaBytesPerMin = stat.MegaBytesPerMin; } #region < Private & Protected Members > private decimal BytesPerSecField = 0; private decimal MegaBytesPerMinField = 0; /// This toggle Enables/Disables firing the Event to avoid firing it when doing multiple consecutive changes to the values protected bool EnablePropertyChangeEvent { get; set; } = true; #endregion #region < Public Properties & Events > /// This event will fire when the value of the SpeedStatistic is updated public event PropertyChangedEventHandler PropertyChanged; /// Raise Property Change Event protected void OnPropertyChange(string PropertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName)); /// public virtual decimal BytesPerSec { get => BytesPerSecField; protected set { if (BytesPerSecField != value) { BytesPerSecField = value; if (EnablePropertyChangeEvent) OnPropertyChange("MegaBytesPerMin"); } } } /// public virtual decimal MegaBytesPerMin { get => MegaBytesPerMinField; protected set { if (MegaBytesPerMinField != value) { MegaBytesPerMinField = value; if (EnablePropertyChangeEvent) OnPropertyChange("MegaBytesPerMin"); } } } #endregion #region < Methods > /// /// Returns a string that represents the current object. /// public override string ToString() { return $"Speed: {BytesPerSec} Bytes/sec{Environment.NewLine}Speed: {MegaBytesPerMin} MegaBytes/min"; } /// public virtual SpeedStatistic Clone() => new SpeedStatistic(this); object ICloneable.Clone() => Clone(); internal static SpeedStatistic Parse(string line1, string line2) { var res = new SpeedStatistic(); var pattern = new Regex(@"\d+([\.,]\d+)?"); Match match; match = pattern.Match(line1); if (match.Success) { res.BytesPerSec = Convert.ToDecimal(match.Value.Replace(',', '.'), CultureInfo.InvariantCulture); } match = pattern.Match(line2); if (match.Success) { res.MegaBytesPerMin = Convert.ToDecimal(match.Value.Replace(',', '.'), CultureInfo.InvariantCulture); } return res; } #endregion } /// /// This object represents the Average of several objects, and contains /// methods to facilitate that functionality. /// public sealed class AverageSpeedStatistic : SpeedStatistic { #region < Constructors > /// /// Initialize a new object with the default values. /// public AverageSpeedStatistic() : base() { } /// /// Initialize a new object.
/// Values will be set to the return values of and
///
/// /// Either a or a object.
/// If a is passed into this constructor, it wil be treated as the base instead. /// public AverageSpeedStatistic(ISpeedStatistic speedStat) : base() { Divisor = 1; Combined_BytesPerSec = speedStat.BytesPerSec; Combined_MegaBytesPerMin = speedStat.MegaBytesPerMin; CalculateAverage(); } /// /// Initialize a new object using .
///
/// /// public AverageSpeedStatistic(IEnumerable speedStats) : base() { Average(speedStats); } /// /// Clone an AverageSpeedStatistic /// public AverageSpeedStatistic(AverageSpeedStatistic stat) : base(stat) { Divisor = stat.Divisor; Combined_BytesPerSec = stat.BytesPerSec; Combined_MegaBytesPerMin = stat.MegaBytesPerMin; } #endregion #region < Fields > /// Sum of all private decimal Combined_BytesPerSec = 0; /// Sum of all private decimal Combined_MegaBytesPerMin = 0; /// Total number of SpeedStats that were combined to produce the Combined_* values private long Divisor = 0; #endregion #region < Public Methods > /// public override SpeedStatistic Clone() => new AverageSpeedStatistic(this); #endregion #region < Reset Value Methods > /// /// Set the values for this object to 0 /// #if !NET40 [MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)] #endif public void Reset() { Combined_BytesPerSec = 0; Combined_MegaBytesPerMin = 0; Divisor = 0; BytesPerSec = 0; MegaBytesPerMin = 0; } /// /// Set the values for this object to 0 /// #if !NET40 [MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)] #endif internal void Reset(bool enablePropertyChangeEvent) { EnablePropertyChangeEvent = enablePropertyChangeEvent; Reset(); EnablePropertyChangeEvent = true; } #endregion // Add / Subtract methods are internal to allow usage within the RoboCopyResultsList object. // The 'Average' Methods will first Add the statistics to the current one, then recalculate the average. // Subtraction is only used when an item is removed from a RoboCopyResultsList // As such, public consumers should typically not require the use of subtract methods #region < ADD ( internal ) > /// /// Add the results of the supplied SpeedStatistic objects to this object.
/// Does not automatically recalculate the average, and triggers no events. ///
/// /// If any supplied Speedstat object is actually an object, default functionality will combine the private fields /// used to calculate the average speed instead of using the publicly reported speeds.
/// This ensures that combining the average of multiple objects returns the correct value.
/// Ex: One object with 2 runs and one with 3 runs will return the average of all 5 runs instead of the average of two averages. ///
/// SpeedStatistic Item to add /// /// Setting this to TRUE will instead combine the calculated average of the , treating it as a single object.
/// Ignore the private fields, and instead use the calculated speeds) /// #if !NET40 [MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)] #endif internal void Add(ISpeedStatistic stat, bool ForceTreatAsSpeedStat = false) { if (stat == null) return; bool IsAverageStat = !ForceTreatAsSpeedStat && stat.GetType() == typeof(AverageSpeedStatistic); AverageSpeedStatistic AvgStat = IsAverageStat ? (AverageSpeedStatistic)stat : null; Divisor += IsAverageStat ? AvgStat.Divisor : 1; Combined_BytesPerSec += IsAverageStat ? AvgStat.Combined_BytesPerSec : stat.BytesPerSec; Combined_MegaBytesPerMin += IsAverageStat ? AvgStat.Combined_MegaBytesPerMin : stat.MegaBytesPerMin; } /// /// Add the supplied SpeedStatistic collection to this object. /// /// SpeedStatistic collection to add /// /// #if !NET40 [MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)] #endif internal void Add(IEnumerable stats, bool ForceTreatAsSpeedStat = false) { foreach (SpeedStatistic stat in stats) Add(stat, ForceTreatAsSpeedStat); } #endregion #region < Subtract ( internal ) > /// /// Subtract the results of the supplied SpeedStatistic objects from this object.
///
/// Statistics Item to add /// #if !NET40 [MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)] #endif internal void Subtract(SpeedStatistic stat, bool ForceTreatAsSpeedStat = false) { if (stat == null) return; bool IsAverageStat = !ForceTreatAsSpeedStat && stat.GetType() == typeof(AverageSpeedStatistic); AverageSpeedStatistic AvgStat = IsAverageStat ? (AverageSpeedStatistic)stat : null; Divisor -= IsAverageStat ? AvgStat.Divisor : 1; //Combine the values if Divisor is still valid if (Divisor >= 1) { Combined_BytesPerSec -= IsAverageStat ? AvgStat.Combined_BytesPerSec : stat.BytesPerSec; Combined_MegaBytesPerMin -= IsAverageStat ? AvgStat.Combined_MegaBytesPerMin : stat.MegaBytesPerMin; } //Cannot have negative speeds or divisors -> Reset all values if (Divisor < 1 || Combined_BytesPerSec < 0 || Combined_MegaBytesPerMin < 0) { Combined_BytesPerSec = 0; Combined_MegaBytesPerMin = 0; Divisor = 0; } } /// /// Subtract the supplied SpeedStatistic collection from this object. /// /// SpeedStatistic collection to subtract /// #if !NET40 [MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)] #endif internal void Subtract(IEnumerable stats, bool ForceTreatAsSpeedStat = false) { foreach (SpeedStatistic stat in stats) Subtract(stat, ForceTreatAsSpeedStat); } #endregion #region < AVERAGE ( public ) > /// /// Immediately recalculate the BytesPerSec and MegaBytesPerMin values /// #if !NET40 [MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)] #endif internal void CalculateAverage() { EnablePropertyChangeEvent = false; //BytesPerSec var i = Divisor < 1 ? 0 : Math.Round(Combined_BytesPerSec / Divisor, 3); bool TriggerBPS = BytesPerSec != i; BytesPerSec = i; //MegaBytes i = Divisor < 1 ? 0 : Math.Round(Combined_MegaBytesPerMin / Divisor, 3); bool TriggerMBPM = MegaBytesPerMin != i; MegaBytesPerMin = i; //Trigger Events EnablePropertyChangeEvent = true; if (TriggerBPS) OnPropertyChange("BytesPerSec"); if (TriggerMBPM) OnPropertyChange("MegaBytesPerMin"); } /// /// Combine the supplied objects, then get the average. /// /// Stats object /// public void Average(ISpeedStatistic stat) { Add(stat); CalculateAverage(); } /// /// Combine the supplied objects, then get the average. /// /// Collection of objects /// public void Average(IEnumerable stats) { Add(stats); CalculateAverage(); } /// New Statistics Object /// public static AverageSpeedStatistic GetAverage(IEnumerable stats) { AverageSpeedStatistic stat = new AverageSpeedStatistic(); stat.Average(stats); return stat; } #endregion } }