From c082d4203b46cfd1a70ec4d4fec4d453a650e9e3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 27 Nov 2025 16:58:32 +0000 Subject: [PATCH] Changes before error encountered Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- .../Common/CompressionProgress.cs | 43 +++++++ .../IO/ProgressReportingStream.cs | 119 ++++++++++++++++++ src/SharpCompress/Writers/AbstractWriter.cs | 18 +++ src/SharpCompress/Writers/WriterOptions.cs | 7 ++ 4 files changed, 187 insertions(+) create mode 100644 src/SharpCompress/Common/CompressionProgress.cs create mode 100644 src/SharpCompress/IO/ProgressReportingStream.cs diff --git a/src/SharpCompress/Common/CompressionProgress.cs b/src/SharpCompress/Common/CompressionProgress.cs new file mode 100644 index 00000000..67f47931 --- /dev/null +++ b/src/SharpCompress/Common/CompressionProgress.cs @@ -0,0 +1,43 @@ +namespace SharpCompress.Common; + +/// +/// Represents progress information for compression operations. +/// +public sealed class CompressionProgress +{ + /// + /// Initializes a new instance of the class. + /// + /// The path of the entry being compressed. + /// Number of bytes read from the source. + /// Total bytes to be read from the source, or null if unknown. + public CompressionProgress(string entryPath, long bytesRead, long? totalBytes) + { + EntryPath = entryPath; + BytesRead = bytesRead; + TotalBytes = totalBytes; + } + + /// + /// Gets the path of the entry being compressed. + /// + public string EntryPath { get; } + + /// + /// Gets the number of bytes read from the source so far. + /// + public long BytesRead { get; } + + /// + /// Gets the total number of bytes to be read from the source, or null if unknown. + /// + public long? TotalBytes { get; } + + /// + /// Gets the progress percentage (0-100), or null if total bytes is unknown. + /// + public double? PercentComplete => + TotalBytes.HasValue && TotalBytes.Value > 0 + ? (double)BytesRead / TotalBytes.Value * 100 + : null; +} diff --git a/src/SharpCompress/IO/ProgressReportingStream.cs b/src/SharpCompress/IO/ProgressReportingStream.cs new file mode 100644 index 00000000..9192f9ca --- /dev/null +++ b/src/SharpCompress/IO/ProgressReportingStream.cs @@ -0,0 +1,119 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Common; + +namespace SharpCompress.IO; + +/// +/// A stream wrapper that reports progress as data is written. +/// +internal sealed class ProgressReportingStream : Stream +{ + private readonly Stream _baseStream; + private readonly IProgress _progress; + private readonly string _entryPath; + private readonly long? _totalBytes; + private long _bytesWritten; + + public ProgressReportingStream( + Stream baseStream, + IProgress progress, + string entryPath, + long? totalBytes + ) + { + _baseStream = baseStream; + _progress = progress; + _entryPath = entryPath; + _totalBytes = totalBytes; + } + + public override bool CanRead => _baseStream.CanRead; + + public override bool CanSeek => _baseStream.CanSeek; + + public override bool CanWrite => _baseStream.CanWrite; + + public override long Length => _baseStream.Length; + + public override long Position + { + get => _baseStream.Position; + set => _baseStream.Position = value; + } + + public override void Flush() => _baseStream.Flush(); + + public override int Read(byte[] buffer, int offset, int count) => + _baseStream.Read(buffer, offset, count); + + public override long Seek(long offset, SeekOrigin origin) => + _baseStream.Seek(offset, origin); + + public override void SetLength(long value) => _baseStream.SetLength(value); + + public override void Write(byte[] buffer, int offset, int count) + { + _baseStream.Write(buffer, offset, count); + _bytesWritten += count; + ReportProgress(); + } + + public override void Write(ReadOnlySpan buffer) + { + _baseStream.Write(buffer); + _bytesWritten += buffer.Length; + ReportProgress(); + } + + public override async Task WriteAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken + ) + { + await _baseStream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); + _bytesWritten += count; + ReportProgress(); + } + + public override async ValueTask WriteAsync( + ReadOnlyMemory buffer, + CancellationToken cancellationToken = default + ) + { + await _baseStream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false); + _bytesWritten += buffer.Length; + ReportProgress(); + } + + public override void WriteByte(byte value) + { + _baseStream.WriteByte(value); + _bytesWritten++; + ReportProgress(); + } + + private void ReportProgress() + { + _progress.Report(new CompressionProgress(_entryPath, _bytesWritten, _totalBytes)); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _baseStream.Dispose(); + } + base.Dispose(disposing); + } + + public override async ValueTask DisposeAsync() + { + await _baseStream.DisposeAsync().ConfigureAwait(false); + await base.DisposeAsync().ConfigureAwait(false); + } +} diff --git a/src/SharpCompress/Writers/AbstractWriter.cs b/src/SharpCompress/Writers/AbstractWriter.cs index e75b93b6..467fcb6b 100644 --- a/src/SharpCompress/Writers/AbstractWriter.cs +++ b/src/SharpCompress/Writers/AbstractWriter.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using SharpCompress.Common; +using SharpCompress.IO; namespace SharpCompress.Writers; @@ -22,6 +23,23 @@ public abstract class AbstractWriter(ArchiveType type, WriterOptions writerOptio protected WriterOptions WriterOptions { get; } = writerOptions; + /// + /// Wraps the source stream with a progress-reporting stream if progress reporting is enabled. + /// + /// The source stream to wrap. + /// The path of the entry being written. + /// A stream that reports progress, or the original stream if progress is not enabled. + protected Stream WrapWithProgress(Stream source, string entryPath) + { + if (WriterOptions.Progress is null) + { + return source; + } + + long? totalBytes = source.CanSeek ? source.Length : null; + return new ProgressReportingStream(source, WriterOptions.Progress, entryPath, totalBytes); + } + public abstract void Write(string filename, Stream source, DateTime? modificationTime); public virtual async Task WriteAsync( diff --git a/src/SharpCompress/Writers/WriterOptions.cs b/src/SharpCompress/Writers/WriterOptions.cs index 361dfb55..4611dabc 100644 --- a/src/SharpCompress/Writers/WriterOptions.cs +++ b/src/SharpCompress/Writers/WriterOptions.cs @@ -1,3 +1,4 @@ +using System; using SharpCompress.Common; using D = SharpCompress.Compressors.Deflate; @@ -36,6 +37,12 @@ public class WriterOptions : OptionsBase /// public int CompressionLevel { get; set; } + /// + /// An optional progress reporter for tracking compression operations. + /// When set, progress updates will be reported as entries are written. + /// + public IProgress? Progress { get; set; } + public static implicit operator WriterOptions(CompressionType compressionType) => new(compressionType); }