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

266 lines
9.3 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using RoboSharp.Interfaces;
using RoboSharp.EventArgObjects;
using RoboSharp.Results;
using WhereToAdd = RoboSharp.Results.ProgressEstimator.WhereToAdd;
using System.Runtime.CompilerServices;
namespace RoboSharp.Results
{
/// <summary>
/// Updates the Statistics every 250ms
/// </summary>
/// <remarks>
/// <see href="https://github.com/tjscience/RoboSharp/wiki/RoboQueueProgressEstimator"/>
/// </remarks>
internal class RoboQueueProgressEstimator : IProgressEstimator, IResults, IDisposable
{
#region < Constructors >
internal RoboQueueProgressEstimator()
{
tmpDirs = new Statistic(Statistic.StatType.Directories);
tmpFiles = new Statistic(Statistic.StatType.Files);
tmpBytes = new Statistic(Statistic.StatType.Bytes);
tmpDirs.EnablePropertyChangeEvent = false;
tmpFiles.EnablePropertyChangeEvent = false;
tmpBytes.EnablePropertyChangeEvent = false;
}
#endregion
#region < Private Members >
//ThreadSafe Bags/Queues
private readonly ConcurrentDictionary<IProgressEstimator, object> SubscribedStats = new ConcurrentDictionary<IProgressEstimator, object>();
//Stats
private readonly Statistic DirStatField = new Statistic(Statistic.StatType.Directories, "Directory Stats Estimate");
private readonly Statistic FileStatsField = new Statistic(Statistic.StatType.Files, "File Stats Estimate");
private readonly Statistic ByteStatsField = new Statistic(Statistic.StatType.Bytes, "Byte Stats Estimate");
//Add Tasks
private int UpdatePeriodInMilliSecond = 250;
private readonly Statistic tmpDirs;
private readonly Statistic tmpFiles;
private readonly Statistic tmpBytes;
private DateTime NextUpdate = DateTime.Now;
private readonly object StatLock = new object();
private readonly object UpdateLock = new object();
private bool disposedValue;
#endregion
#region < Public Properties >
/// <summary>
/// Estimate of current number of directories processed while the job is still running. <br/>
/// Estimate is provided by parsing of the LogLines produces by RoboCopy.
/// </summary>
public IStatistic DirectoriesStatistic => DirStatField;
/// <summary>
/// Estimate of current number of files processed while the job is still running. <br/>
/// Estimate is provided by parsing of the LogLines produces by RoboCopy.
/// </summary>
public IStatistic FilesStatistic => FileStatsField;
/// <summary>
/// Estimate of current number of bytes processed while the job is still running. <br/>
/// Estimate is provided by parsing of the LogLines produces by RoboCopy.
/// </summary>
public IStatistic BytesStatistic => ByteStatsField;
RoboCopyExitStatus IResults.Status => new RoboCopyExitStatus((int)GetExitCode());
/// <inheritdoc cref="IProgressEstimator.ValuesUpdated"/>
public event ProgressEstimator.UIUpdateEventHandler ValuesUpdated;
#endregion
#region < Public Methods >
/// <summary>
/// Parse this object's stats into a <see cref="RoboCopyExitCodes"/> enum.
/// </summary>
/// <returns></returns>
public RoboCopyExitCodes GetExitCode()
{
Results.RoboCopyExitCodes code = 0;
//Files Copied
if (FilesStatistic.Copied > 0)
code |= Results.RoboCopyExitCodes.FilesCopiedSuccessful;
//Extra
if (DirectoriesStatistic.Extras > 0 || FilesStatistic.Extras > 0)
code |= Results.RoboCopyExitCodes.ExtraFilesOrDirectoriesDetected;
//MisMatch
if (DirectoriesStatistic.Mismatch > 0 || FilesStatistic.Mismatch > 0)
code |= Results.RoboCopyExitCodes.MismatchedDirectoriesDetected;
//Failed
if (DirectoriesStatistic.Failed > 0 || FilesStatistic.Failed > 0)
code |= Results.RoboCopyExitCodes.SomeFilesOrDirectoriesCouldNotBeCopied;
return code;
}
#endregion
#region < Counting Methods ( private ) >
/// <summary>
/// Subscribe to the update events of a <see cref="ProgressEstimator"/> object
/// </summary>
internal void BindToProgressEstimator(IProgressEstimator estimator)
{
if (!SubscribedStats.ContainsKey(estimator))
{
SubscribedStats.TryAdd(estimator, null);
estimator.ValuesUpdated += Estimator_ValuesUpdated;
}
}
private void Estimator_ValuesUpdated(IProgressEstimator sender, IProgressEstimatorUpdateEventArgs e)
{
Statistic bytes = null;
Statistic files = null;
Statistic dirs = null;
bool updateReqd = false;
//Wait indefinitely until the tmp stats are released -- Previously -> lock(StatLock) { }
Monitor.Enter(StatLock);
//Update the Temp Stats
tmpBytes.AddStatistic(e.ValueChange_Bytes);
tmpFiles.AddStatistic(e.ValueChange_Files);
tmpDirs.AddStatistic(e.ValueChange_Directories);
//Check if an update should be pushed to the public fields
//If another thread already has this locked, then it is pushing out an update -> This thread exits the method after releasing StatLock
if (Monitor.TryEnter(UpdateLock))
{
updateReqd = DateTime.Now >= NextUpdate && (tmpFiles.NonZeroValue || tmpDirs.NonZeroValue || tmpBytes.NonZeroValue);
if (updateReqd)
{
if (tmpBytes.NonZeroValue)
{
bytes = tmpBytes.Clone();
tmpBytes.Reset();
}
if (tmpFiles.NonZeroValue)
{
files = tmpFiles.Clone();
tmpFiles.Reset();
}
if (tmpDirs.NonZeroValue)
{
dirs = tmpDirs.Clone();
tmpDirs.Reset();
}
}
}
Monitor.Exit(StatLock); //Release the tmp Stats lock
if (updateReqd)
{
// Perform the Add Events
if (bytes != null) ByteStatsField.AddStatistic(bytes);
if (files != null) FileStatsField.AddStatistic(files);
if (dirs != null) DirStatField.AddStatistic(dirs);
//Raise the event, then update the NextUpdate time period
ValuesUpdated?.Invoke(this, new IProgressEstimatorUpdateEventArgs(this, bytes, files, dirs));
NextUpdate = DateTime.Now.AddMilliseconds(UpdatePeriodInMilliSecond);
}
//Release the UpdateLock
if (Monitor.IsEntered(UpdateLock))
Monitor.Exit(UpdateLock);
}
/// <summary>
/// Unsubscribe from all bound Statistic objects
/// </summary>
internal void UnBind()
{
if (SubscribedStats != null)
{
foreach (var est in SubscribedStats.Keys)
{
est.ValuesUpdated -= Estimator_ValuesUpdated;
}
}
}
#endregion
#region < CancelTasks & DisposePattern >
/// <summary>
/// Unbind all the ProgressEstimators
/// </summary>
internal void CancelTasks() => CancelTasks(true);
private void CancelTasks(bool RunUpdateTask)
{
//Preventn any additional events coming through
UnBind();
//Push the last update out after a short delay to allow any pending events through
if (RunUpdateTask)
{
Task.Run( async () => {
lock (UpdateLock)
NextUpdate = DateTime.Now.AddMilliseconds(124);
await Task.Delay(125);
Estimator_ValuesUpdated(null, IProgressEstimatorUpdateEventArgs.DummyArgs);
});
}
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects)
}
//Cancel the tasks
CancelTasks(false);
disposedValue = true;
}
}
// TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
~RoboQueueProgressEstimator()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#endregion
}
}