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