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 { /// /// Updates the Statistics every 250ms /// /// /// /// 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 SubscribedStats = new ConcurrentDictionary(); //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 > /// /// Estimate of current number of directories processed while the job is still running.
/// Estimate is provided by parsing of the LogLines produces by RoboCopy. ///
public IStatistic DirectoriesStatistic => DirStatField; /// /// Estimate of current number of files processed while the job is still running.
/// Estimate is provided by parsing of the LogLines produces by RoboCopy. ///
public IStatistic FilesStatistic => FileStatsField; /// /// Estimate of current number of bytes processed while the job is still running.
/// Estimate is provided by parsing of the LogLines produces by RoboCopy. ///
public IStatistic BytesStatistic => ByteStatsField; RoboCopyExitStatus IResults.Status => new RoboCopyExitStatus((int)GetExitCode()); /// public event ProgressEstimator.UIUpdateEventHandler ValuesUpdated; #endregion #region < Public Methods > /// /// Parse this object's stats into a enum. /// /// 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 ) > /// /// Subscribe to the update events of a object /// 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); } /// /// Unsubscribe from all bound Statistic objects /// internal void UnBind() { if (SubscribedStats != null) { foreach (var est in SubscribedStats.Keys) { est.ValuesUpdated -= Estimator_ValuesUpdated; } } } #endregion #region < CancelTasks & DisposePattern > /// /// Unbind all the ProgressEstimators /// 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 } }