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
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ngbrown/f33d7e7ca882e9b783836147c5451d14 to your computer and use it in GitHub Desktop.
Save ngbrown/f33d7e7ca882e9b783836147c5451d14 to your computer and use it in GitHub Desktop.
Console progress bar. Code is under the MIT License: http://opensource.org/licenses/MIT
using System;
using System.Threading;
static class Program {
static void Main() {
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.");
}
}
using System;
using System.Text;
using System.Threading;
/// <summary>
/// An ASCII progress bar
/// </summary>
/// <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();
}
}
}
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);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment