817 lines
33 KiB
C#
817 lines
33 KiB
C#
using System;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Runtime.CompilerServices;
|
|
using RoboSharp.Interfaces;
|
|
using RoboSharp.EventArgObjects;
|
|
|
|
namespace RoboSharp.Results
|
|
{
|
|
/// <summary>
|
|
/// Information about number of items Copied, Skipped, Failed, etc.
|
|
/// <para/>
|
|
/// <see cref="RoboCopyResults"/> will not typically raise any events, but this object is used for other items, such as <see cref="ProgressEstimator"/> and <see cref="RoboCopyResultsList"/> to present results whose values may update periodically.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <see href="https://github.com/tjscience/RoboSharp/wiki/Statistic"/>
|
|
/// </remarks>
|
|
public class Statistic : IStatistic
|
|
{
|
|
internal static IStatistic Default_Bytes = new Statistic(type: StatType.Bytes);
|
|
internal static IStatistic Default_Files = new Statistic(type: StatType.Files);
|
|
internal static IStatistic Default_Dirs = new Statistic(type: StatType.Directories);
|
|
|
|
#region < Constructors >
|
|
|
|
/// <summary> Create a new Statistic object of <see cref="StatType"/> </summary>
|
|
[Obsolete("Statistic Types require Initialization with a StatType")]
|
|
private Statistic() { }
|
|
|
|
/// <summary> Create a new Statistic object </summary>
|
|
public Statistic(StatType type) { Type = type; }
|
|
|
|
/// <summary> Create a new Statistic object </summary>
|
|
public Statistic(StatType type, string name) { Type = type; Name = name; }
|
|
|
|
/// <summary> Clone an existing Statistic object</summary>
|
|
public Statistic(Statistic stat)
|
|
{
|
|
Type = stat.Type;
|
|
NameField = stat.Name;
|
|
TotalField = stat.Total;
|
|
CopiedField = stat.Copied;
|
|
SkippedField = stat.Skipped;
|
|
MismatchField = stat.Mismatch;
|
|
FailedField = stat.Failed;
|
|
ExtrasField = stat.Extras;
|
|
}
|
|
|
|
/// <summary> Clone an existing Statistic object</summary>
|
|
internal Statistic(StatType type, string name, long total, long copied, long skipped, long mismatch, long failed, long extras)
|
|
{
|
|
Type = type;
|
|
NameField = name;
|
|
TotalField = total;
|
|
CopiedField = copied;
|
|
SkippedField = skipped;
|
|
MismatchField = mismatch;
|
|
FailedField = failed;
|
|
ExtrasField = extras;
|
|
}
|
|
|
|
/// <summary> Describe the Type of Statistics Object </summary>
|
|
public enum StatType
|
|
{
|
|
/// <summary> Statistics object represents count of Directories </summary>
|
|
Directories,
|
|
/// <summary> Statistics object represents count of Files </summary>
|
|
Files,
|
|
/// <summary> Statistics object represents a Size ( number of bytes )</summary>
|
|
Bytes
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region < Fields >
|
|
|
|
private string NameField = "";
|
|
private long TotalField = 0;
|
|
private long CopiedField = 0;
|
|
private long SkippedField = 0;
|
|
private long MismatchField = 0;
|
|
private long FailedField = 0;
|
|
private long ExtrasField = 0;
|
|
|
|
#endregion
|
|
|
|
#region < Events >
|
|
|
|
/// <summary> This toggle Enables/Disables firing the <see cref="PropertyChanged"/> Event to avoid firing it when doing multiple consecutive changes to the values </summary>
|
|
internal bool EnablePropertyChangeEvent = true;
|
|
private bool PropertyChangedListener => EnablePropertyChangeEvent && PropertyChanged != null;
|
|
private bool Listener_TotalChanged => EnablePropertyChangeEvent && OnTotalChanged != null;
|
|
private bool Listener_CopiedChanged => EnablePropertyChangeEvent && OnCopiedChanged != null;
|
|
private bool Listener_SkippedChanged => EnablePropertyChangeEvent && OnSkippedChanged != null;
|
|
private bool Listener_MismatchChanged => EnablePropertyChangeEvent && OnMisMatchChanged != null;
|
|
private bool Listener_FailedChanged => EnablePropertyChangeEvent && OnFailedChanged != null;
|
|
private bool Listener_ExtrasChanged => EnablePropertyChangeEvent && OnExtrasChanged != null;
|
|
|
|
/// <summary>
|
|
/// This event will fire when the value of the statistic is updated via Adding / Subtracting methods. <br/>
|
|
/// Provides <see cref="StatisticPropertyChangedEventArgs"/> object.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Allows use with both binding to controls and <see cref="ProgressEstimator"/> binding. <br/>
|
|
/// EventArgs can be passed into <see cref="AddStatistic(PropertyChangedEventArgs)"/> after casting.
|
|
/// </remarks>
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
/// <summary>Handles any value changes </summary>
|
|
public delegate void StatChangedHandler(Statistic sender, StatChangedEventArg e);
|
|
|
|
/// <summary> Occurs when the <see cref="Total"/> Property is updated. </summary>
|
|
public event StatChangedHandler OnTotalChanged;
|
|
|
|
/// <summary> Occurs when the <see cref="Copied"/> Property is updated. </summary>
|
|
public event StatChangedHandler OnCopiedChanged;
|
|
|
|
/// <summary> Occurs when the <see cref="Skipped"/> Property is updated. </summary>
|
|
public event StatChangedHandler OnSkippedChanged;
|
|
|
|
/// <summary> Occurs when the <see cref="Mismatch"/> Property is updated. </summary>
|
|
public event StatChangedHandler OnMisMatchChanged;
|
|
|
|
/// <summary> Occurs when the <see cref="Failed"/> Property is updated. </summary>
|
|
public event StatChangedHandler OnFailedChanged;
|
|
|
|
/// <summary> Occurs when the <see cref="Extras"/> Property is updated. </summary>
|
|
public event StatChangedHandler OnExtrasChanged;
|
|
|
|
#endregion
|
|
|
|
#region < Properties >
|
|
|
|
/// <summary>
|
|
/// Checks all values and determines if any of them are != 0.
|
|
/// </summary>
|
|
public bool NonZeroValue => TotalField != 0 || CopiedField != 0 || SkippedField != 0 || MismatchField != 0 || FailedField != 0 || ExtrasField != 0;
|
|
|
|
/// <summary>
|
|
/// Name of the Statistics Object
|
|
/// </summary>
|
|
public string Name
|
|
{
|
|
get => NameField;
|
|
set
|
|
{
|
|
if (value != NameField)
|
|
{
|
|
NameField = value ?? "";
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <inheritdoc cref="StatType"/>
|
|
/// </summary>
|
|
public StatType Type { get; }
|
|
|
|
/// <inheritdoc cref="IStatistic.Total"/>
|
|
public long Total {
|
|
get => TotalField;
|
|
internal set
|
|
{
|
|
if (TotalField != value)
|
|
{
|
|
var e = PrepEventArgs(TotalField, value, "Total");
|
|
TotalField = value;
|
|
if (e != null)
|
|
{
|
|
PropertyChanged?.Invoke(this, e.Value.Item2);
|
|
OnTotalChanged?.Invoke(this, e.Value.Item1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc cref="IStatistic.Copied"/>
|
|
public long Copied {
|
|
get => CopiedField;
|
|
internal set
|
|
{
|
|
if (CopiedField != value)
|
|
{
|
|
var e = PrepEventArgs(CopiedField, value, "Copied");
|
|
CopiedField = value;
|
|
if (e != null)
|
|
{
|
|
PropertyChanged?.Invoke(this, e.Value.Item2);
|
|
OnCopiedChanged?.Invoke(this, e.Value.Item1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc cref="IStatistic.Skipped"/>
|
|
public long Skipped {
|
|
get => SkippedField;
|
|
internal set
|
|
{
|
|
if (SkippedField != value)
|
|
{
|
|
var e = PrepEventArgs(SkippedField, value, "Skipped");
|
|
SkippedField = value;
|
|
if (e != null)
|
|
{
|
|
PropertyChanged?.Invoke(this, e.Value.Item2);
|
|
OnSkippedChanged?.Invoke(this, e.Value.Item1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc cref="IStatistic.Mismatch"/>
|
|
public long Mismatch {
|
|
get => MismatchField;
|
|
internal set
|
|
{
|
|
if (MismatchField != value)
|
|
{
|
|
var e = PrepEventArgs(MismatchField, value, "Mismatch");
|
|
MismatchField = value;
|
|
if (e != null)
|
|
{
|
|
PropertyChanged?.Invoke(this, e.Value.Item2);
|
|
OnMisMatchChanged?.Invoke(this, e.Value.Item1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc cref="IStatistic.Failed"/>
|
|
public long Failed {
|
|
get => FailedField;
|
|
internal set
|
|
{
|
|
if (FailedField != value)
|
|
{
|
|
var e = PrepEventArgs(FailedField, value, "Failed");
|
|
FailedField = value;
|
|
if (e != null)
|
|
{
|
|
PropertyChanged?.Invoke(this, e.Value.Item2);
|
|
OnFailedChanged?.Invoke(this, e.Value.Item1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc cref="IStatistic.Extras"/>
|
|
public long Extras {
|
|
get => ExtrasField;
|
|
internal set
|
|
{
|
|
if (ExtrasField != value)
|
|
{
|
|
var e = PrepEventArgs(ExtrasField, value, "Extras");
|
|
ExtrasField = value;
|
|
if (e != null)
|
|
{
|
|
PropertyChanged?.Invoke(this, e.Value.Item2);
|
|
OnExtrasChanged?.Invoke(this, e.Value.Item1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region < ToString Methods >
|
|
|
|
/// <summary>
|
|
/// Returns a string that represents the current object.
|
|
/// </summary>
|
|
public override string ToString() => ToString(false, true, ", ");
|
|
|
|
/// <summary>
|
|
/// Customize the returned string
|
|
/// </summary>
|
|
/// <param name="IncludeType">Include string representation of <see cref="Type"/></param>
|
|
/// <param name="IncludePrefix">Include "Total:" / "Copied:" / etc in the string to identify the values</param>
|
|
/// <param name="Delimiter">Value Delimieter</param>
|
|
/// <param name="DelimiterAfterType">
|
|
/// Include the delimiter after the 'Type' - Only used if <paramref name="IncludeType"/> us also true. <br/>
|
|
/// When <paramref name="IncludeType"/> is true, a space always exist after the type string. This would add delimiter instead of the space.
|
|
/// </param>
|
|
/// <returns>
|
|
/// TRUE, TRUE, "," --> $"{Type} Total: {Total}, Copied: {Copied}, Skipped: {Skipped}, Mismatch: {Mismatch}, Failed: {Failed}, Extras: {Extras}" <para/>
|
|
/// FALSE, TRUE, "," --> $"Total: {Total}, Copied: {Copied}, Skipped: {Skipped}, Mismatch: {Mismatch}, Failed: {Failed}, Extras: {Extras}" <para/>
|
|
/// FALSE, FALSE, "," --> $"{Total}, {Copied}, {Skipped}, {Mismatch}, {Failed}, {Extras}"
|
|
/// </returns>
|
|
public string ToString(bool IncludeType, bool IncludePrefix, string Delimiter, bool DelimiterAfterType = false)
|
|
{
|
|
return $"{ToString_Type(IncludeType, DelimiterAfterType)}" + $"{(IncludeType && DelimiterAfterType ? Delimiter : "")}" +
|
|
$"{ToString_Total(false, IncludePrefix)}{Delimiter}" +
|
|
$"{ToString_Copied(false, IncludePrefix)}{Delimiter}" +
|
|
$"{ToString_Skipped(false, IncludePrefix)}{Delimiter}" +
|
|
$"{ToString_Mismatch(false, IncludePrefix)}{Delimiter}" +
|
|
$"{ToString_Failed(false, IncludePrefix)}{Delimiter}" +
|
|
$"{ToString_Extras(false, IncludePrefix)}";
|
|
}
|
|
|
|
/// <summary> Get the <see cref="Type"/> as a string </summary>
|
|
public string ToString_Type() => ToString_Type(true).Trim();
|
|
private string ToString_Type(bool IncludeType, bool Trim = false) => IncludeType ? $"{Type}{(Trim? "" : " ")}" : "";
|
|
|
|
/// <summary>Get the string describing the <see cref="Total"/></summary>
|
|
/// <returns></returns>
|
|
/// <inheritdoc cref="ToString(bool, bool, string, bool)"/>
|
|
public string ToString_Total(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix? "Total: " : "")}{Total}";
|
|
|
|
/// <summary>Get the string describing the <see cref="Copied"/></summary>
|
|
/// <inheritdoc cref="ToString_Total"/>
|
|
public string ToString_Copied(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix ? "Copied: " : "")}{Copied}";
|
|
|
|
/// <summary>Get the string describing the <see cref="Extras"/></summary>
|
|
/// <inheritdoc cref="ToString_Total"/>
|
|
public string ToString_Extras(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix ? "Extras: " : "")}{Extras}";
|
|
|
|
/// <summary>Get the string describing the <see cref="Failed"/></summary>
|
|
/// <inheritdoc cref="ToString_Total"/>
|
|
public string ToString_Failed(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix ? "Failed: " : "")}{Failed}";
|
|
|
|
/// <summary>Get the string describing the <see cref="Mismatch"/></summary>
|
|
/// <inheritdoc cref="ToString_Total"/>
|
|
public string ToString_Mismatch(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix ? "Mismatch: " : "")}{Mismatch}";
|
|
|
|
/// <summary>Get the string describing the <see cref="Skipped"/></summary>
|
|
/// <inheritdoc cref="ToString_Total"/>
|
|
public string ToString_Skipped(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix ? "Skipped: " : "")}{Skipped}";
|
|
|
|
#endregion
|
|
|
|
#region < Parsing Methods >
|
|
|
|
/// <summary>
|
|
/// Parse a string and for the tokens reported by RoboCopy
|
|
/// </summary>
|
|
/// <param name="type">Statistic Type to produce</param>
|
|
/// <param name="line">LogLine produced by RoboCopy in Summary Section</param>
|
|
/// <returns>New Statistic Object</returns>
|
|
public static Statistic Parse(StatType type, string line)
|
|
{
|
|
var res = new Statistic(type);
|
|
|
|
var tokenNames = new[] { nameof(Total), nameof(Copied), nameof(Skipped), nameof(Mismatch), nameof(Failed), nameof(Extras) };
|
|
var patternBuilder = new StringBuilder(@"^.*:");
|
|
|
|
foreach (var tokenName in tokenNames)
|
|
{
|
|
var tokenPattern = GetTokenPattern(tokenName);
|
|
patternBuilder.Append(@"\s+").Append(tokenPattern);
|
|
}
|
|
|
|
var pattern = patternBuilder.ToString();
|
|
var match = Regex.Match(line, pattern);
|
|
if (!match.Success)
|
|
return res;
|
|
|
|
var props = res.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
|
|
foreach (var tokenName in tokenNames)
|
|
{
|
|
var prop = props.FirstOrDefault(x => x.Name == tokenName);
|
|
if (prop == null)
|
|
continue;
|
|
|
|
var tokenString = match.Groups[tokenName].Value;
|
|
var tokenValue = ParseTokenString(tokenString);
|
|
prop.SetValue(res, tokenValue, null);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
private static string GetTokenPattern(string tokenName)
|
|
{
|
|
return $@"(?<{tokenName}>[\d\.]+(\s\w)?)";
|
|
}
|
|
|
|
private static long ParseTokenString(string tokenString)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(tokenString))
|
|
return 0;
|
|
|
|
tokenString = tokenString.Trim();
|
|
if (Regex.IsMatch(tokenString, @"^\d+$", RegexOptions.Compiled))
|
|
return long.Parse(tokenString);
|
|
|
|
var match = Regex.Match(tokenString, @"(?<Mains>[\d\.,]+)(\.(?<Fraction>\d+))\s(?<Unit>\w)", RegexOptions.Compiled);
|
|
if (match.Success)
|
|
{
|
|
var mains = match.Groups["Mains"].Value.Replace(".", "").Replace(",", "");
|
|
var fraction = match.Groups["Fraction"].Value;
|
|
var unit = match.Groups["Unit"].Value.ToLower();
|
|
|
|
var number = double.Parse($"{mains}.{fraction}", NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture);
|
|
switch (unit)
|
|
{
|
|
case "k":
|
|
// Byte = kBytes * 1024
|
|
number *= Math.Pow(1024, 1);
|
|
break;
|
|
case "m":
|
|
// Byte = MBytes * 1024 * 1024
|
|
number *= Math.Pow(1024, 2);
|
|
break;
|
|
case "g":
|
|
// Byte = GBytes * 1024 * 1024 * 1024
|
|
number *= Math.Pow(1024, 3);
|
|
break;
|
|
case "t":
|
|
// Byte = TBytes * 1024 * 1024 * 1024 * 1024
|
|
number *= Math.Pow(1024, 4);
|
|
break;
|
|
}
|
|
|
|
return Convert.ToInt64(number);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region < Reset Method >
|
|
|
|
/// <summary>
|
|
/// Set the values for this object to 0
|
|
/// </summary>
|
|
public void Reset(bool enablePropertyChangeEvent)
|
|
{
|
|
EnablePropertyChangeEvent = enablePropertyChangeEvent;
|
|
Reset();
|
|
EnablePropertyChangeEvent = true;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Reset all values to Zero ( 0 ) -- Used by <see cref="RoboCopyResultsList"/> for the properties
|
|
/// </summary>
|
|
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
|
public void Reset()
|
|
{
|
|
Statistic OriginalValues = PropertyChangedListener && NonZeroValue ? this.Clone() : null;
|
|
|
|
//Total
|
|
var eTotal = ConditionalPrepEventArgs(Listener_TotalChanged, TotalField, 0, "Total");
|
|
TotalField = 0;
|
|
//Copied
|
|
var eCopied = ConditionalPrepEventArgs(Listener_CopiedChanged, CopiedField, 0, "Copied");
|
|
CopiedField = 0;
|
|
//Extras
|
|
var eExtras = ConditionalPrepEventArgs(Listener_ExtrasChanged, ExtrasField, 0, "Extras");
|
|
ExtrasField = 0;
|
|
//Failed
|
|
var eFailed = ConditionalPrepEventArgs(Listener_FailedChanged, FailedField, 0, "Failed");
|
|
FailedField = 0;
|
|
//Mismatch
|
|
var eMismatch = ConditionalPrepEventArgs(Listener_MismatchChanged, MismatchField, 0, "Mismatch");
|
|
MismatchField = 0;
|
|
//Skipped
|
|
var eSkipped = ConditionalPrepEventArgs(Listener_SkippedChanged, SkippedField, 0, "Skipped");
|
|
SkippedField = 0;
|
|
|
|
//Trigger Events
|
|
TriggerDeferredEvents(OriginalValues, eTotal, eCopied, eExtras, eFailed, eMismatch, eSkipped);
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
#region < Trigger Deferred Events >
|
|
|
|
/// <summary>
|
|
/// Prep Event Args for SETTERS of the properties
|
|
/// </summary>
|
|
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
|
private Lazy<Tuple<StatChangedEventArg, StatisticPropertyChangedEventArgs>> PrepEventArgs(long OldValue, long NewValue, string PropertyName)
|
|
{
|
|
if (!EnablePropertyChangeEvent) return null;
|
|
var old = this.Clone();
|
|
return new Lazy<Tuple<StatChangedEventArg, StatisticPropertyChangedEventArgs>>(() =>
|
|
{
|
|
StatChangedEventArg e1 = new StatChangedEventArg(this, OldValue, NewValue, PropertyName);
|
|
var e2 = new StatisticPropertyChangedEventArgs(this, old, PropertyName);
|
|
return new Tuple<StatChangedEventArg, StatisticPropertyChangedEventArgs>(e1, e2);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prep event args for the ADD and RESET methods
|
|
/// </summary>
|
|
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
|
private StatChangedEventArg ConditionalPrepEventArgs(bool Listener, long OldValue, long NewValue, string PropertyName)=> !Listener || OldValue == NewValue ? null : new StatChangedEventArg(this, OldValue, NewValue, PropertyName);
|
|
|
|
/// <summary>
|
|
/// Raises the events that were deferred while item was object was still being calculated by ADD / RESET
|
|
/// </summary>
|
|
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
|
private void TriggerDeferredEvents(Statistic OriginalValues, StatChangedEventArg eTotal, StatChangedEventArg eCopied, StatChangedEventArg eExtras, StatChangedEventArg eFailed, StatChangedEventArg eMismatch, StatChangedEventArg eSkipped)
|
|
{
|
|
//Perform Events
|
|
int i = 0;
|
|
if (eTotal != null)
|
|
{
|
|
i = 1;
|
|
OnTotalChanged?.Invoke(this, eTotal);
|
|
}
|
|
if (eCopied != null)
|
|
{
|
|
i += 2;
|
|
OnCopiedChanged?.Invoke(this, eCopied);
|
|
}
|
|
if (eExtras != null)
|
|
{
|
|
i += 4;
|
|
OnExtrasChanged?.Invoke(this, eExtras);
|
|
}
|
|
if (eFailed != null)
|
|
{
|
|
i += 8;
|
|
OnFailedChanged?.Invoke(this, eFailed);
|
|
}
|
|
if (eMismatch != null)
|
|
{
|
|
i += 16;
|
|
OnMisMatchChanged?.Invoke(this, eMismatch);
|
|
}
|
|
if (eSkipped != null)
|
|
{
|
|
i += 32;
|
|
OnSkippedChanged?.Invoke(this, eSkipped);
|
|
}
|
|
|
|
//Trigger PropertyChangeEvent
|
|
if (OriginalValues != null)
|
|
{
|
|
switch (i)
|
|
{
|
|
case 1: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eTotal.PropertyName)); return;
|
|
case 2: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eCopied.PropertyName)); return;
|
|
case 4: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eExtras.PropertyName)); return;
|
|
case 8: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eFailed.PropertyName)); return;
|
|
case 16: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eMismatch.PropertyName)); return;
|
|
case 32: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eSkipped.PropertyName)); return;
|
|
default: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, String.Empty)); return;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region < ADD Methods >
|
|
|
|
/// <summary>
|
|
/// Add the supplied values to this Statistic object. <br/>
|
|
/// Events are defered until all the fields have been added together.
|
|
/// </summary>
|
|
/// <param name="total"></param>
|
|
/// <param name="copied"></param>
|
|
/// <param name="extras"></param>
|
|
/// <param name="failed"></param>
|
|
/// <param name="mismatch"></param>
|
|
/// <param name="skipped"></param>
|
|
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
|
public void Add(long total = 0, long copied = 0, long extras = 0, long failed = 0, long mismatch = 0, long skipped = 0)
|
|
{
|
|
//Store the original object values for the event args
|
|
Statistic originalValues = PropertyChangedListener ? this.Clone() : null;
|
|
|
|
//Total
|
|
long i = TotalField;
|
|
TotalField += total;
|
|
var eTotal = ConditionalPrepEventArgs(Listener_TotalChanged, i, TotalField, "Total");
|
|
|
|
//Copied
|
|
i = CopiedField;
|
|
CopiedField += copied;
|
|
var eCopied = ConditionalPrepEventArgs(Listener_CopiedChanged, i, CopiedField, "Copied");
|
|
|
|
//Extras
|
|
i = ExtrasField;
|
|
ExtrasField += extras;
|
|
var eExtras = ConditionalPrepEventArgs(Listener_ExtrasChanged, i, ExtrasField, "Extras");
|
|
|
|
//Failed
|
|
i = FailedField;
|
|
FailedField += failed;
|
|
var eFailed = ConditionalPrepEventArgs(Listener_FailedChanged, i, FailedField, "Failed");
|
|
|
|
//Mismatch
|
|
i = MismatchField;
|
|
MismatchField += mismatch;
|
|
var eMismatch = ConditionalPrepEventArgs(Listener_MismatchChanged, i, MismatchField, "Mismatch");
|
|
|
|
//Skipped
|
|
i = SkippedField;
|
|
SkippedField += skipped;
|
|
var eSkipped = ConditionalPrepEventArgs(Listener_SkippedChanged, i, SkippedField, "Skipped");
|
|
|
|
//Trigger Events
|
|
if (EnablePropertyChangeEvent)
|
|
TriggerDeferredEvents(originalValues, eTotal, eCopied, eExtras, eFailed, eMismatch, eSkipped);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add the results of the supplied Statistics object to this Statistics object. <br/>
|
|
/// Events are defered until all the fields have been added together.
|
|
/// </summary>
|
|
/// <param name="stat">Statistics Item to add</param>
|
|
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
|
public void AddStatistic(IStatistic stat)
|
|
{
|
|
if (stat != null && stat.Type == this.Type && stat.NonZeroValue)
|
|
Add(stat.Total, stat.Copied, stat.Extras, stat.Failed, stat.Mismatch, stat.Skipped);
|
|
}
|
|
|
|
|
|
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
|
|
/// <param name="enablePropertyChangedEvent"><inheritdoc cref="EnablePropertyChangeEvent" path="*"/></param>
|
|
/// <inheritdoc cref="AddStatistic(IStatistic)"/>
|
|
internal void AddStatistic(IStatistic stats, bool enablePropertyChangedEvent)
|
|
{
|
|
EnablePropertyChangeEvent = enablePropertyChangedEvent;
|
|
AddStatistic(stats);
|
|
EnablePropertyChangeEvent = true;
|
|
|
|
}
|
|
#pragma warning restore CS1573
|
|
|
|
|
|
/// <summary>
|
|
/// Add the results of the supplied Statistics objects to this Statistics object.
|
|
/// </summary>
|
|
/// <param name="stats">Statistics Item to add</param>
|
|
public void AddStatistic(IEnumerable<IStatistic> stats)
|
|
{
|
|
foreach (Statistic stat in stats)
|
|
{
|
|
EnablePropertyChangeEvent = stat == stats.Last();
|
|
AddStatistic(stat);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds <see cref="StatChangedEventArg.Difference"/> to the appropriate property based on the 'PropertyChanged' value. <br/>
|
|
/// Will only add the value if the <see cref="StatChangedEventArg.StatType"/> == <see cref="Type"/>.
|
|
/// </summary>
|
|
/// <param name="eventArgs">Arg provided by either <see cref="PropertyChanged"/> or a Statistic's object's On*Changed events</param>
|
|
public void AddStatistic(PropertyChangedEventArgs eventArgs)
|
|
{
|
|
//Only process the args if of the proper type
|
|
var e = eventArgs as IStatisticPropertyChangedEventArg;
|
|
if (e == null) return;
|
|
|
|
if (e.StatType != this.Type || (e.Is_StatisticPropertyChangedEventArgs && e.Is_StatChangedEventArg))
|
|
{
|
|
// INVALID!
|
|
}
|
|
else if (e.Is_StatisticPropertyChangedEventArgs)
|
|
{
|
|
var e1 = (StatisticPropertyChangedEventArgs)eventArgs;
|
|
AddStatistic(e1.Difference);
|
|
}
|
|
else if (e.Is_StatChangedEventArg)
|
|
{
|
|
var e2 = (StatChangedEventArg)eventArgs;
|
|
switch (e.PropertyName)
|
|
{
|
|
case "": //String.Empty means all fields have changed
|
|
AddStatistic(e2.Sender);
|
|
break;
|
|
case "Copied":
|
|
this.Copied += e2.Difference;
|
|
break;
|
|
case "Extras":
|
|
this.Extras += e2.Difference;
|
|
break;
|
|
case "Failed":
|
|
this.Failed += e2.Difference;
|
|
break;
|
|
case "Mismatch":
|
|
this.Mismatch += e2.Difference;
|
|
break;
|
|
case "Skipped":
|
|
this.Skipped += e2.Difference;
|
|
break;
|
|
case "Total":
|
|
this.Total += e2.Difference;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Combine the results of the supplied statistics objects of the specified type.
|
|
/// </summary>
|
|
/// <param name="stats">Collection of <see cref="Statistic"/> objects</param>
|
|
/// <param name="statType">Create a new Statistic object of this type.</param>
|
|
/// <returns>New Statistics Object</returns>
|
|
public static Statistic AddStatistics(IEnumerable<IStatistic> stats, StatType statType)
|
|
{
|
|
Statistic ret = new Statistic(statType);
|
|
ret.AddStatistic(stats.Where(s => s.Type == statType) );
|
|
return ret;
|
|
}
|
|
|
|
|
|
#endregion ADD
|
|
|
|
#region < AVERAGE Methods >
|
|
|
|
/// <summary>
|
|
/// Combine the supplied <see cref="Statistic"/> objects, then get the average.
|
|
/// </summary>
|
|
/// <param name="stats">Array of Stats objects</param>
|
|
public void AverageStatistic(IEnumerable<IStatistic> stats)
|
|
{
|
|
this.AddStatistic(stats);
|
|
int cnt = stats.Count() + 1;
|
|
Total /= cnt;
|
|
Copied /= cnt;
|
|
Extras /= cnt;
|
|
Failed /= cnt;
|
|
Mismatch /= cnt;
|
|
Skipped /= cnt;
|
|
|
|
}
|
|
|
|
/// <returns>New Statistics Object</returns>
|
|
/// <inheritdoc cref=" AverageStatistic(IEnumerable{IStatistic})"/>
|
|
public static Statistic AverageStatistics(IEnumerable<IStatistic> stats, StatType statType)
|
|
{
|
|
Statistic stat = AddStatistics(stats, statType);
|
|
int cnt = stats.Count(s => s.Type == statType);
|
|
if (cnt > 1)
|
|
{
|
|
stat.Total /= cnt;
|
|
stat.Copied /= cnt;
|
|
stat.Extras /= cnt;
|
|
stat.Failed /= cnt;
|
|
stat.Mismatch /= cnt;
|
|
stat.Skipped /= cnt;
|
|
}
|
|
return stat;
|
|
}
|
|
|
|
#endregion AVERAGE
|
|
|
|
#region < Subtract Methods >
|
|
|
|
/// <summary>
|
|
/// Subtract Method used by <see cref="RoboCopyResultsList"/> <br/>
|
|
/// Events are deferred until all value changes have completed.
|
|
/// </summary>
|
|
/// <param name="stat">Statistics Item to subtract</param>
|
|
#if !NET40
|
|
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
|
#endif
|
|
public void Subtract(IStatistic stat)
|
|
{
|
|
if (stat.Type == this.Type && stat.NonZeroValue)
|
|
Add(-stat.Total, -stat.Copied, -stat.Extras, -stat.Failed, -stat.Mismatch, -stat.Skipped);
|
|
}
|
|
|
|
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
|
|
/// <param name="enablePropertyChangedEvent"><inheritdoc cref="EnablePropertyChangeEvent" path="*"/></param>
|
|
/// <inheritdoc cref="Subtract(IStatistic)"/>
|
|
internal void Subtract(IStatistic stats, bool enablePropertyChangedEvent)
|
|
{
|
|
EnablePropertyChangeEvent = enablePropertyChangedEvent;
|
|
Subtract(stats);
|
|
EnablePropertyChangeEvent = true;
|
|
}
|
|
#pragma warning restore CS1573
|
|
|
|
/// <summary>
|
|
/// Subtract the results of the supplied Statistics objects to this Statistics object.
|
|
/// </summary>
|
|
/// <param name="stats">Statistics Item to subtract</param>
|
|
public void Subtract(IEnumerable<IStatistic> stats)
|
|
{
|
|
foreach (Statistic stat in stats)
|
|
{
|
|
EnablePropertyChangeEvent = stat == stats.Last();
|
|
Subtract(stat);
|
|
}
|
|
}
|
|
|
|
/// <param name="MainStat">Statistics object to clone</param>
|
|
/// <param name="stats"><inheritdoc cref="Subtract(IStatistic)"/></param>
|
|
/// <returns>Clone of the <paramref name="MainStat"/> object with the <paramref name="stats"/> subtracted from it.</returns>
|
|
/// <inheritdoc cref="Subtract(IStatistic)"/>
|
|
public static Statistic Subtract(IStatistic MainStat, IStatistic stats)
|
|
{
|
|
var ret = MainStat.Clone();
|
|
ret.Subtract(stats);
|
|
return ret;
|
|
}
|
|
|
|
#endregion Subtract
|
|
|
|
|
|
/// <inheritdoc cref="IStatistic.Clone"/>
|
|
public Statistic Clone() => new Statistic(this);
|
|
|
|
object ICloneable.Clone() => new Statistic(this);
|
|
|
|
}
|
|
}
|