using System; using System.IO; namespace PSMinIO.Utils { /// /// Stream wrapper that tracks read progress and reports it via callback /// public class ProgressTrackingStream : Stream { private readonly Stream _baseStream; private readonly long _totalBytes; private readonly Action _progressCallback; private long _bytesRead = 0; private long _lastReportedBytes; private readonly long _reportingThreshold; private readonly long _startPosition; public ProgressTrackingStream(Stream baseStream, long totalBytes, Action progressCallback) { _baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream)); _totalBytes = totalBytes; _progressCallback = progressCallback ?? throw new ArgumentNullException(nameof(progressCallback)); _startPosition = baseStream.Position; // Only report progress every 1% or 1MB, whichever is smaller (reduces overhead) _reportingThreshold = Math.Min(totalBytes / 100, 1024 * 1024); if (_reportingThreshold < 8192) _reportingThreshold = 8192; // Minimum 8KB threshold } public override bool CanRead => _baseStream.CanRead; public override bool CanSeek => _baseStream.CanSeek; public override bool CanWrite => _baseStream.CanWrite; public override long Length => _totalBytes; public override long Position { get => _bytesRead; set => throw new NotSupportedException("Setting position is not supported"); } public override int Read(byte[] buffer, int offset, int count) { // Limit read to remaining bytes var remainingBytes = _totalBytes - _bytesRead; var bytesToRead = (int)Math.Min(count, remainingBytes); if (bytesToRead <= 0) return 0; var bytesRead = _baseStream.Read(buffer, offset, bytesToRead); _bytesRead += bytesRead; // Report progress only when threshold is reached or at completion (reduces overhead) if (_bytesRead - _lastReportedBytes >= _reportingThreshold || _bytesRead >= _totalBytes) { _progressCallback(_bytesRead, _totalBytes); _lastReportedBytes = _bytesRead; } return bytesRead; } public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException("Writing is not supported"); } public override void Flush() { _baseStream.Flush(); } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException("Seeking is not supported"); } public override void SetLength(long value) { throw new NotSupportedException("Setting length is not supported"); } protected override void Dispose(bool disposing) { if (disposing) { // Don't dispose the base stream - let the caller handle it } base.Dispose(disposing); } } }