Skip to content

Instantly share code, notes, and snippets.

@ngbrown
Forked from DanielSWolf/Program.cs
Last active April 3, 2020 03:32
Show Gist options
  • Select an option

  • Save ngbrown/f33d7e7ca882e9b783836147c5451d14 to your computer and use it in GitHub Desktop.

Select an option

Save ngbrown/f33d7e7ca882e9b783836147c5451d14 to your computer and use it in GitHub Desktop.

Revisions

  1. ngbrown revised this gist Apr 3, 2020. 1 changed file with 246 additions and 0 deletions.
    246 changes: 246 additions & 0 deletions ProgressStream.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,246 @@
    using System;
    using System.IO;
    using System.Threading;
    using System.Threading.Tasks;

    public class ProgressStream : Stream
    {
    private Stream stream;
    private readonly IProgress<double> progress;
    private readonly bool leaveOpen;
    private long trackedLength;
    private long trackedPosition;
    private int? beginCount;

    public ProgressStream(Stream stream, IProgress<double> progress, long? length = null, bool leaveOpen = false)
    {
    this.stream = stream;
    this.progress = progress;
    this.leaveOpen = leaveOpen;

    this.trackedLength = length ?? stream.Length;

    if (this.trackedLength == 0)
    {
    throw new InvalidOperationException("Stream must have valid length");
    }
    }

    public override void CopyTo(Stream destination, int bufferSize)
    {
    this.stream.CopyTo(destination, bufferSize);
    }

    public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
    {
    return this.stream.CopyToAsync(destination, bufferSize, cancellationToken);
    }

    public override void Close()
    {
    this.Dispose(true);
    }

    public override void Flush()
    {
    this.stream.Flush();
    }

    public override Task FlushAsync(CancellationToken cancellationToken)
    {
    return this.stream.FlushAsync(cancellationToken);
    }

    public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback,
    object state)
    {
    return this.stream.BeginRead(buffer, offset, count, callback, state);
    }

    public override int EndRead(IAsyncResult asyncResult)
    {
    int bytesRead = this.stream.EndRead(asyncResult);
    return AdvanceProgress(bytesRead);
    }

    public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
    {
    var bytesRead = await this.stream.ReadAsync(buffer, offset, count, cancellationToken);
    return AdvanceProgress(bytesRead);
    }

    public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = new CancellationToken())
    {
    var bytesRead = await base.ReadAsync(buffer, cancellationToken);
    return AdvanceProgress(bytesRead);
    }

    public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback,
    object state)
    {
    this.beginCount = count;
    return this.stream.BeginWrite(buffer, offset, count, callback, state);
    }

    public override void EndWrite(IAsyncResult asyncResult)
    {
    this.stream.EndWrite(asyncResult);
    if (asyncResult.IsCompleted && this.beginCount.HasValue)
    {
    AdvanceProgress(this.beginCount.Value);
    this.beginCount = default;
    }
    }

    public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
    {
    await this.stream.WriteAsync(buffer, offset, count, cancellationToken);
    AdvanceProgress(count);
    }

    public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = new CancellationToken())
    {
    await base.WriteAsync(buffer, cancellationToken);
    AdvanceProgress(buffer.Length);
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
    var position = this.stream.Seek(offset, origin);
    switch (origin)
    {
    case SeekOrigin.Begin:
    this.trackedPosition = position;
    break;
    case SeekOrigin.Current:
    this.trackedPosition += this.trackedLength;
    break;
    case SeekOrigin.End:
    this.trackedPosition = this.trackedLength + offset;
    break;
    default:
    throw new ArgumentOutOfRangeException(nameof(origin), origin, null);
    }
    this.ReportProgress();

    return position;
    }

    public override void SetLength(long value)
    {
    this.stream.SetLength(value);
    this.trackedLength = value;
    this.ReportProgress();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
    int bytesRead = this.stream.Read(buffer, offset, count);
    return this.AdvanceProgress(bytesRead);
    }

    public override int Read(Span<byte> buffer)
    {
    return AdvanceProgress(this.stream.Read(buffer));
    }

    public override int ReadByte()
    {
    int readByte = this.stream.ReadByte();
    this.AdvanceProgress(readByte < 0 ? 0 : 1);
    return readByte;
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
    this.stream.Write(buffer, offset, count);
    }

    public override void Write(ReadOnlySpan<byte> buffer)
    {
    this.stream.Write(buffer);
    AdvanceProgress(buffer.Length);
    }

    public override void WriteByte(byte value)
    {
    this.stream.WriteByte(value);
    }

    public override bool CanRead => this.stream.CanRead;

    public override bool CanSeek => this.stream.CanSeek;

    public override bool CanTimeout => this.stream.CanTimeout;

    public override bool CanWrite => this.stream.CanWrite;

    public override long Length => this.stream.Length;

    public override long Position
    {
    get => this.stream.Position;
    set
    {
    this.stream.Position = value;
    this.trackedPosition = value;
    this.ReportProgress();
    }
    }

    public override int ReadTimeout
    {
    get => this.stream.ReadTimeout;
    set => this.stream.ReadTimeout = value;
    }

    public override int WriteTimeout
    {
    get => this.stream.WriteTimeout;
    set => this.stream.WriteTimeout = value;
    }

    public override async ValueTask DisposeAsync()
    {
    await this.stream.DisposeAsync();
    this.stream = null;
    await base.DisposeAsync();
    }

    protected override void Dispose(bool disposing)
    {
    try
    {
    if (this.leaveOpen || !disposing || this.stream == null)
    {
    return;
    }

    this.stream.Close();
    }
    finally
    {
    this.stream = (Stream) null;
    base.Dispose(disposing);
    }
    }

    private long AdvanceProgress(long bytesRead)
    {
    this.trackedPosition += bytesRead;
    this.ReportProgress();
    return bytesRead;
    }

    private int AdvanceProgress(int bytesRead)
    {
    this.trackedPosition += bytesRead;
    this.ReportProgress();
    return bytesRead;
    }

    private void ReportProgress()
    {
    this.progress.Report((double) this.trackedPosition / this.trackedLength);
    }
    }
  2. ngbrown revised this gist Feb 21, 2020. 1 changed file with 102 additions and 83 deletions.
    185 changes: 102 additions & 83 deletions ProgressBar.cs
    Original file line number Diff line number Diff line change
    @@ -5,86 +5,105 @@
    /// <summary>
    /// An ASCII progress bar
    /// </summary>
    public class ProgressBar : IDisposable, IProgress<double> {
    private const int blockCount = 10;
    private readonly TimeSpan animationInterval = TimeSpan.FromSeconds(1.0 / 8);
    private const string animation = @"|/-\";

    private readonly Timer timer;

    private double currentProgress = 0;
    private string currentText = string.Empty;
    private bool disposed = false;
    private int animationIndex = 0;

    public ProgressBar() {
    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 (!Console.IsOutputRedirected) {
    ResetTimer();
    }
    }

    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);
    }

    private void TimerHandler(object state) {
    lock (timer) {
    if (disposed) return;

    int progressBlockCount = (int) (currentProgress * blockCount);
    int percent = (int) (currentProgress * 100);
    string text = string.Format("[{0}{1}] {2,3}% {3}",
    new string('#', progressBlockCount), new string('-', blockCount - progressBlockCount),
    percent,
    animation[animationIndex++ % animation.Length]);
    UpdateText(text);

    ResetTimer();
    }
    }

    private void UpdateText(string text) {
    // Get length of common portion
    int commonPrefixLength = 0;
    int commonLength = Math.Min(currentText.Length, text.Length);
    while (commonPrefixLength < commonLength && text[commonPrefixLength] == currentText[commonPrefixLength]) {
    commonPrefixLength++;
    }

    // Backtrack to the first differing character
    StringBuilder 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
    int overlapCount = currentText.Length - text.Length;
    if (overlapCount > 0) {
    outputBuilder.Append(' ', overlapCount);
    outputBuilder.Append('\b', overlapCount);
    }

    Console.Write(outputBuilder);
    currentText = text;
    }

    private void ResetTimer() {
    timer.Change(animationInterval, TimeSpan.FromMilliseconds(-1));
    }

    public void Dispose() {
    lock (timer) {
    disposed = true;
    UpdateText(string.Empty);
    }
    }

    }
    /// <remarks>
    /// Code is under the MIT License
    /// Based on https://gist.github.com/DanielSWolf/0ab6a96899cc5377bf54 by Daniel Wolf
    /// </remarks>
    public class ProgressBar : IDisposable, IProgress<double>
    {
    private const int blockCount = 20;
    private readonly TimeSpan animationInterval = TimeSpan.FromSeconds(1.0 / 8);
    private const string animation = @"|/-\";

    private readonly Timer timer;

    private double currentProgress = 0;
    private string currentText = string.Empty;
    private bool disposed = false;
    private int animationIndex = 0;

    public ProgressBar()
    {
    // 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 (Console.IsOutputRedirected)
    {
    this.disposed = true;
    }
    else
    {
    this.timer = new Timer(TimerHandler, default(object), this.animationInterval, this.animationInterval);
    }
    }

    public void Report(double value)
    {
    // Make sure value is in [0..1] range
    double clipped = Math.Max(0, Math.Min(1, value));
    Interlocked.Exchange(ref this.currentProgress, clipped);
    }

    private void TimerHandler(object state)
    {
    lock (this.timer)
    {
    if (this.disposed)
    {
    return;
    }

    int progressBlockCount = (int) (this.currentProgress * blockCount);
    int percent = (int) (this.currentProgress * 100);
    string filled = new string('#', progressBlockCount);
    string empty = new string(' ', blockCount - progressBlockCount);
    string text = string.Format("[{0}{1}] {2,3}% {3}",
    filled, empty, percent, animation[this.animationIndex++ % animation.Length]);
    UpdateText(text);
    }
    }

    private void UpdateText(string text)
    {
    // Get length of common portion
    int commonPrefixLength = 0;
    int commonLength = Math.Min(this.currentText.Length, text.Length);
    while (commonPrefixLength < commonLength && text[commonPrefixLength] == this.currentText[commonPrefixLength])
    {
    commonPrefixLength++;
    }

    // Backtrack to the first differing character
    var outputBuilder = new StringBuilder();
    outputBuilder.Append('\b', this.currentText.Length - commonPrefixLength);

    // Output new suffix
    outputBuilder.Append(text.Substring(commonPrefixLength));

    // If the new text is shorter than the old one: delete overlapping characters
    int overlapCount = this.currentText.Length - text.Length;
    if (overlapCount > 0)
    {
    outputBuilder.Append(' ', overlapCount);
    outputBuilder.Append('\b', overlapCount);
    }

    Console.Write(outputBuilder);
    this.currentText = text;
    }

    public void Dispose()
    {
    if (this.disposed)
    {
    return;
    }

    lock (this.timer)
    {
    this.disposed = true;
    UpdateText(string.Empty);
    this.timer.Dispose();
    }
    }
    }
  3. @DanielSWolf DanielSWolf revised this gist Jul 25, 2015. No changes.
  4. @DanielSWolf DanielSWolf revised this gist Jul 25, 2015. No changes.
  5. @DanielSWolf DanielSWolf revised this gist Jul 25, 2015. No changes.
  6. @DanielSWolf DanielSWolf revised this gist Jul 2, 2015. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions Program.cs
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,6 @@
    static class Program {

    static void Main() {
    Thread.Sleep(5000);
    Console.Write("Performing some task... ");
    using (var progress = new ProgressBar()) {
    for (int i = 0; i <= 100; i++) {
    @@ -13,7 +12,6 @@ static void Main() {
    }
    }
    Console.WriteLine("Done.");
    Console.ReadLine();
    }

    }
  7. @DanielSWolf DanielSWolf created this gist Jul 2, 2015.
    19 changes: 19 additions & 0 deletions Program.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,19 @@
    using System;
    using System.Threading;

    static class Program {

    static void Main() {
    Thread.Sleep(5000);
    Console.Write("Performing some task... ");
    using (var progress = new ProgressBar()) {
    for (int i = 0; i <= 100; i++) {
    progress.Report((double) i / 100);
    Thread.Sleep(20);
    }
    }
    Console.WriteLine("Done.");
    Console.ReadLine();
    }

    }
    90 changes: 90 additions & 0 deletions ProgressBar.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,90 @@
    using System;
    using System.Text;
    using System.Threading;

    /// <summary>
    /// An ASCII progress bar
    /// </summary>
    public class ProgressBar : IDisposable, IProgress<double> {
    private const int blockCount = 10;
    private readonly TimeSpan animationInterval = TimeSpan.FromSeconds(1.0 / 8);
    private const string animation = @"|/-\";

    private readonly Timer timer;

    private double currentProgress = 0;
    private string currentText = string.Empty;
    private bool disposed = false;
    private int animationIndex = 0;

    public ProgressBar() {
    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 (!Console.IsOutputRedirected) {
    ResetTimer();
    }
    }

    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);
    }

    private void TimerHandler(object state) {
    lock (timer) {
    if (disposed) return;

    int progressBlockCount = (int) (currentProgress * blockCount);
    int percent = (int) (currentProgress * 100);
    string text = string.Format("[{0}{1}] {2,3}% {3}",
    new string('#', progressBlockCount), new string('-', blockCount - progressBlockCount),
    percent,
    animation[animationIndex++ % animation.Length]);
    UpdateText(text);

    ResetTimer();
    }
    }

    private void UpdateText(string text) {
    // Get length of common portion
    int commonPrefixLength = 0;
    int commonLength = Math.Min(currentText.Length, text.Length);
    while (commonPrefixLength < commonLength && text[commonPrefixLength] == currentText[commonPrefixLength]) {
    commonPrefixLength++;
    }

    // Backtrack to the first differing character
    StringBuilder 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
    int overlapCount = currentText.Length - text.Length;
    if (overlapCount > 0) {
    outputBuilder.Append(' ', overlapCount);
    outputBuilder.Append('\b', overlapCount);
    }

    Console.Write(outputBuilder);
    currentText = text;
    }

    private void ResetTimer() {
    timer.Change(animationInterval, TimeSpan.FromMilliseconds(-1));
    }

    public void Dispose() {
    lock (timer) {
    disposed = true;
    UpdateText(string.Empty);
    }
    }

    }