Changes before error encountered

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-11-27 16:58:32 +00:00
parent d4380b6bb6
commit c082d4203b
4 changed files with 187 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
namespace SharpCompress.Common;
/// <summary>
/// Represents progress information for compression operations.
/// </summary>
public sealed class CompressionProgress
{
/// <summary>
/// Initializes a new instance of the <see cref="CompressionProgress"/> class.
/// </summary>
/// <param name="entryPath">The path of the entry being compressed.</param>
/// <param name="bytesRead">Number of bytes read from the source.</param>
/// <param name="totalBytes">Total bytes to be read from the source, or null if unknown.</param>
public CompressionProgress(string entryPath, long bytesRead, long? totalBytes)
{
EntryPath = entryPath;
BytesRead = bytesRead;
TotalBytes = totalBytes;
}
/// <summary>
/// Gets the path of the entry being compressed.
/// </summary>
public string EntryPath { get; }
/// <summary>
/// Gets the number of bytes read from the source so far.
/// </summary>
public long BytesRead { get; }
/// <summary>
/// Gets the total number of bytes to be read from the source, or null if unknown.
/// </summary>
public long? TotalBytes { get; }
/// <summary>
/// Gets the progress percentage (0-100), or null if total bytes is unknown.
/// </summary>
public double? PercentComplete =>
TotalBytes.HasValue && TotalBytes.Value > 0
? (double)BytesRead / TotalBytes.Value * 100
: null;
}

View File

@@ -0,0 +1,119 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
namespace SharpCompress.IO;
/// <summary>
/// A stream wrapper that reports progress as data is written.
/// </summary>
internal sealed class ProgressReportingStream : Stream
{
private readonly Stream _baseStream;
private readonly IProgress<CompressionProgress> _progress;
private readonly string _entryPath;
private readonly long? _totalBytes;
private long _bytesWritten;
public ProgressReportingStream(
Stream baseStream,
IProgress<CompressionProgress> 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<byte> 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<byte> 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);
}
}

View File

@@ -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;
/// <summary>
/// Wraps the source stream with a progress-reporting stream if progress reporting is enabled.
/// </summary>
/// <param name="source">The source stream to wrap.</param>
/// <param name="entryPath">The path of the entry being written.</param>
/// <returns>A stream that reports progress, or the original stream if progress is not enabled.</returns>
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(

View File

@@ -1,3 +1,4 @@
using System;
using SharpCompress.Common;
using D = SharpCompress.Compressors.Deflate;
@@ -36,6 +37,12 @@ public class WriterOptions : OptionsBase
/// </summary>
public int CompressionLevel { get; set; }
/// <summary>
/// An optional progress reporter for tracking compression operations.
/// When set, progress updates will be reported as entries are written.
/// </summary>
public IProgress<CompressionProgress>? Progress { get; set; }
public static implicit operator WriterOptions(CompressionType compressionType) =>
new(compressionType);
}