| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 |
- using System;
- using System.Text;
- using System.Threading;
- using Quadarax.Foundation.Core.Object;
- // ReSharper disable InconsistentNaming
- namespace Quadarax.Foundation.Core.Console
- {
- public class ProgressBar : DisposableObject, IProgress<double>
- {
- #region *** Constants ***
- private const int CN_BLOCK_COUNT = 10;
- private const string CS_ANIMATION = @"|/-\";
- #endregion
- #region *** Private fields ***
- private readonly TimeSpan _animationInterval = TimeSpan.FromSeconds(1.0 / 8);
- private readonly Timer _timer;
- private double _currentProgress;
- private string _currentText = string.Empty;
- private int _animationIndex;
- private ConsoleWriter _writer;
- #endregion
- #region *** Constructors ***
- public ProgressBar(ConsoleWriter writer)
- {
- _writer = writer ?? throw new ArgumentNullException(nameof(writer));
- _timer = new Timer(TimerHandler);
- // A progress bar is only for temporary display in a console window.
- // If the console output is redirected to a file, draw nothing.
- // Otherwise, we'll end up with a lot of garbage in the target file.
- if (!System.Console.IsOutputRedirected)
- {
- ResetTimer();
- }
- }
- #endregion
- #region *** Public Operations ***
- /// <summary>
- /// Update progress status.
- /// </summary>
- /// <param name="value">Status value between 0 - 1. Represents percentage.</param>
- public void Report(double value)
- {
- // Make sure value is in [0..1] range
- value = Math.Max(0, Math.Min(1, value));
- Interlocked.Exchange(ref _currentProgress, value);
- }
- #endregion
- #region *** Private Operations ***
- private void TimerHandler(object state)
- {
- lock (_timer)
- {
- var progressBlockCount = (int)(_currentProgress * CN_BLOCK_COUNT);
- var percent = (int)(_currentProgress * 100);
- var text = $"[{new string('#', progressBlockCount)}{new string('-', CN_BLOCK_COUNT - progressBlockCount)}] {percent,3}% {CS_ANIMATION[_animationIndex++ % CS_ANIMATION.Length]}";
- UpdateText(text);
- ResetTimer();
- }
- }
- private void UpdateText(string text)
- {
- // Get length of common portion
- var commonPrefixLength = 0;
- var commonLength = Math.Min(_currentText.Length, text.Length);
- while (commonPrefixLength < commonLength && text[commonPrefixLength] == _currentText[commonPrefixLength])
- {
- commonPrefixLength++;
- }
- // Backtrack to the first differing character
- var outputBuilder = new StringBuilder();
- outputBuilder.Append('\b', _currentText.Length - commonPrefixLength);
- // Output new suffix
- outputBuilder.Append(text.Substring(commonPrefixLength));
- // If the new text is shorter than the old one: delete overlapping characters
- var overlapCount = _currentText.Length - text.Length;
- if (overlapCount > 0)
- {
- outputBuilder.Append(' ', overlapCount);
- outputBuilder.Append('\b', overlapCount);
- }
- _writer.Write(outputBuilder.ToString());
- _currentText = text;
- }
- private void ResetTimer()
- {
- _timer.Change(_animationInterval, TimeSpan.FromMilliseconds(-1));
- }
- #endregion
- #region *** Private Overrides ***
- protected override void OnDisposing()
- {
- lock (_timer)
- {
- UpdateText(string.Empty);
- _writer = null;
- }
- }
- #endregion
- }
- }
|