using System; using System.IO; using System.Threading; using System.Threading.Tasks; public class ProgressStream : Stream { private Stream stream; private readonly IProgress progress; private readonly bool leaveOpen; private long trackedLength; private long trackedPosition; private int? beginCount; public ProgressStream(Stream stream, IProgress 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 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 ReadAsync(Memory 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 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 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 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); } }