// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.Linq; using System.Threading; namespace Flax.Deps { /// /// Class to display download progress /// public class ProgressDisplay : IDisposable { /// /// Current progress /// private long _progress; /// /// Current display thread /// private readonly Thread _asyncDisplayThread; /// /// Progress display console position /// private readonly int _cursorPos; /// /// Queue of diffs from progress /// /// /// Queue last second of data /// private readonly Queue _progressQueue = new Queue(); /// /// Last progress to calculate progress diff /// private long _lastProgress; /// /// Should thread be alive /// private bool _isAlive = true; /// /// Total file size in bytes /// public long TotalFileSizeBytes { get; private set; } /// /// Current progress of task /// public long Progress { get => _progress; set { if (value < _progress) { throw new Exception("Value cannot be smaller then current progress."); } _progress = value; } } /// /// Gets a value indicating whether can use system console for progress reporting. /// public static bool CanUseConsole { get { return false; /* try { // App crashes on Azure DevOps with exception: // The handle is invalid. // at // at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) // at System.Console.GetBufferInfo(Boolean throwOnNoConsole, Boolean & succeeded) // at Flax.Deps.ProgressDisplay..ctor(Int64 totalFileSizeBytes) int a = Console.CursorTop; Console.SetCursorPosition(0, a + 1); } catch (Exception ex) { Console.WriteLine("No ProgressDisplay support."); Console.WriteLine(ex.Message); Console.WriteLine(); Console.WriteLine(ex.StackTrace); Console.WriteLine(); return false; } return true; */ } } /// /// Initializes a new instance of the class. /// /// The total file size bytes. public ProgressDisplay(long totalFileSizeBytes) { TotalFileSizeBytes = totalFileSizeBytes; _cursorPos = Console.CursorTop; Console.SetCursorPosition(0, _cursorPos + 1); _asyncDisplayThread = new Thread(Display); _asyncDisplayThread.Start(); } /// /// Updates the progress. /// /// The progress. /// The total length. public void Update(long progress, long totalLength) { TotalFileSizeBytes = totalLength <= 0 ? progress : totalLength; _progress = progress; } /// /// Updates console text with current progress. /// private void Display() { lock (_asyncDisplayThread) { while (_isAlive) { // Set queue progress _progressQueue.Enqueue(Progress - _lastProgress); // Remember last cursor position var cursorPosTop = Console.CursorTop; var cursorPosLeft = Console.CursorLeft; // Set cursor position at progress display location Console.SetCursorPosition(0, _cursorPos); var display = $"Downloading: {(Progress / (1024 * 1024f)):0.00} MB / {(TotalFileSizeBytes / (1024 * 1024f)):0.00} - {_progressQueue.Sum() / (1024 * 1024f):0.00} MB/s"; // TODO add blank space fillings at the end of line if previous line was longer Console.WriteLine(display); // Set back previous position Console.SetCursorPosition(cursorPosLeft, cursorPosTop); // Remove old entries from queue if (_progressQueue.Count > 10) { _progressQueue.Dequeue(); } _lastProgress = Progress; // Wait 1/10 of second Thread.Sleep(100); } } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { _isAlive = false; } } }