From 3a73cfe9258c1490713d94f17d3da3b9a34a90d0 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 11 Jun 2026 17:24:42 +0100 Subject: [PATCH 1/7] Add instance based extraction size instead of just static --- docs/API.md | 10 + docs/USAGE.md | 7 +- .../Archives/IArchiveEntryExtensions.cs | 38 +++- src/SharpCompress/Common/ExtractionOptions.cs | 5 + .../Common/Options/IExtractionOptions.cs | 5 + .../Readers/AbstractReader.Async.cs | 14 +- src/SharpCompress/Readers/AbstractReader.cs | 13 +- .../Readers/IReaderExtensions.cs | 45 ++++- .../OptionsUsabilityTests.cs | 172 ++++++++++++++++++ 9 files changed, 287 insertions(+), 22 deletions(-) diff --git a/docs/API.md b/docs/API.md index 6e6efce1..0fbcb27f 100644 --- a/docs/API.md +++ b/docs/API.md @@ -315,6 +315,9 @@ var safeOptions = ExtractionOptions.SafeExtract; // No overwrite var flatOptions = ExtractionOptions.FlatExtract; // No directory structure var metadataOptions = ExtractionOptions.PreserveMetadata; // Keep timestamps and attributes +// Tune extraction copy buffering +var extractionOptions = new ExtractionOptions { BufferSize = 131072 }; + // Factory defaults: // - file path / FileInfo overloads use LeaveStreamOpen = false // - stream overloads use LeaveStreamOpen = true @@ -334,6 +337,13 @@ var options = new ReaderOptions BufferSize = 81920, RewindableBufferSize = 1_048_576, }; + +var extractionOptions = new ExtractionOptions +{ + ExtractFullPath = true, + Overwrite = true, + BufferSize = 131072, +}; ``` ### WriterOptions diff --git a/docs/USAGE.md b/docs/USAGE.md index 3c91431b..72356614 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -100,7 +100,12 @@ using (var archive = RarArchive.OpenArchive("Test.rar", ReaderOptions.ForFilePat // Simple extraction with RarArchive; this WriteToDirectory pattern works for all archive types archive.WriteToDirectory( @"D:\temp", - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } + new ExtractionOptions + { + ExtractFullPath = true, + Overwrite = true, + BufferSize = 131072, + } ); } ``` diff --git a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs index f89ba345..0f5034c1 100644 --- a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs +++ b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs @@ -17,7 +17,13 @@ public static class IArchiveEntryExtensions /// /// The stream to write the entry content to. /// Optional progress reporter for tracking extraction progress. - public void WriteTo(Stream streamToWriteTo, IProgress? progress = null) + public void WriteTo(Stream streamToWriteTo, IProgress? progress = null) => archiveEntry.WriteTo(streamToWriteTo, null, progress); + + private void WriteTo( + Stream streamToWriteTo, + int? bufferSize, + IProgress? progress = null + ) { if (archiveEntry.IsDirectory) { @@ -26,7 +32,7 @@ public static class IArchiveEntryExtensions using var entryStream = archiveEntry.OpenEntryStream(); var sourceStream = WrapWithProgress(entryStream, archiveEntry, progress); - sourceStream.CopyTo(streamToWriteTo, Constants.BufferSize); + sourceStream.CopyTo(streamToWriteTo, bufferSize ?? Constants.BufferSize); } /// @@ -40,6 +46,18 @@ public static class IArchiveEntryExtensions IProgress? progress = null, CancellationToken cancellationToken = default ) + { + await archiveEntry + .WriteToAsync(streamToWriteTo, Constants.BufferSize, progress, cancellationToken) + .ConfigureAwait(false); + } + + private async ValueTask WriteToAsync( + Stream streamToWriteTo, + int? bufferSize, + IProgress? progress = null, + CancellationToken cancellationToken = default + ) { if (archiveEntry.IsDirectory) { @@ -57,7 +75,7 @@ public static class IArchiveEntryExtensions #endif var sourceStream = WrapWithProgress(entryStream, archiveEntry, progress); await sourceStream - .CopyToAsync(streamToWriteTo, Constants.BufferSize, cancellationToken) + .CopyToAsync(streamToWriteTo, bufferSize ?? Constants.BufferSize, cancellationToken) .ConfigureAwait(false); } } @@ -133,16 +151,18 @@ public static class IArchiveEntryExtensions /// /// Extract to specific file /// - public void WriteToFile(string destinationFileName, ExtractionOptions? options = null) => + public void WriteToFile(string destinationFileName, ExtractionOptions? options = null) + { entry.WriteEntryToFile( destinationFileName, options, (x, fm) => { - using var fs = File.Open(destinationFileName, fm); - entry.WriteTo(fs); + using var fs = File.Open(x, fm); + entry.WriteTo(fs, options?.BufferSize ?? Constants.BufferSize); } ); + } /// /// Extract to specific file asynchronously @@ -158,8 +178,10 @@ public static class IArchiveEntryExtensions options, async (x, fm, ct) => { - using var fs = File.Open(destinationFileName, fm); - await entry.WriteToAsync(fs, null, ct).ConfigureAwait(false); + using var fs = File.Open(x, fm); + await entry + .WriteToAsync(fs, options?.BufferSize, null, ct) + .ConfigureAwait(false); }, cancellationToken ) diff --git a/src/SharpCompress/Common/ExtractionOptions.cs b/src/SharpCompress/Common/ExtractionOptions.cs index 8a3b4859..b99658f8 100644 --- a/src/SharpCompress/Common/ExtractionOptions.cs +++ b/src/SharpCompress/Common/ExtractionOptions.cs @@ -38,6 +38,11 @@ public sealed record ExtractionOptions : IExtractionOptions /// public bool PreserveAttributes { get; set; } + /// + /// Buffer size for extraction stream copy operations. + /// + public int BufferSize { get; set; } = Constants.BufferSize; + /// /// Delegate for writing symbolic links to disk. /// The first parameter is the source path (where the symlink is created). diff --git a/src/SharpCompress/Common/Options/IExtractionOptions.cs b/src/SharpCompress/Common/Options/IExtractionOptions.cs index 8aac6a65..767bfb93 100644 --- a/src/SharpCompress/Common/Options/IExtractionOptions.cs +++ b/src/SharpCompress/Common/Options/IExtractionOptions.cs @@ -30,6 +30,11 @@ public interface IExtractionOptions /// bool PreserveAttributes { get; set; } + /// + /// Buffer size for extraction stream copy operations. + /// + int BufferSize { get; set; } + /// /// Delegate for writing symbolic links to disk. /// The first parameter is the source path (where the symlink is created). diff --git a/src/SharpCompress/Readers/AbstractReader.Async.cs b/src/SharpCompress/Readers/AbstractReader.Async.cs index 3c3e01df..8628c0c2 100644 --- a/src/SharpCompress/Readers/AbstractReader.Async.cs +++ b/src/SharpCompress/Readers/AbstractReader.Async.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using SharpCompress.Common; -using SharpCompress.IO; namespace SharpCompress.Readers; @@ -138,19 +137,19 @@ public abstract partial class AbstractReader _wroteCurrentEntry = true; } - internal async ValueTask WriteAsync(Stream writeStream, CancellationToken cancellationToken) + private async ValueTask WriteAsync(Stream writeStream, CancellationToken cancellationToken) { #if LEGACY_DOTNET using Stream s = await OpenEntryStreamAsync(cancellationToken).ConfigureAwait(false); var sourceStream = WrapWithProgress(s, Entry); await sourceStream - .CopyToAsync(writeStream, Constants.BufferSize, cancellationToken) + .CopyToAsync(writeStream, Options.BufferSize, cancellationToken) .ConfigureAwait(false); #else await using Stream s = await OpenEntryStreamAsync(cancellationToken).ConfigureAwait(false); var sourceStream = WrapWithProgress(s, Entry); await sourceStream - .CopyToAsync(writeStream, Constants.BufferSize, cancellationToken) + .CopyToAsync(writeStream, Options.BufferSize, cancellationToken) .ConfigureAwait(false); #endif } @@ -187,9 +186,7 @@ public abstract partial class AbstractReader /// /// Moves the current async enumerator to the next entry. /// - internal virtual ValueTask NextEntryForCurrentStreamAsync( - CancellationToken cancellationToken - ) + private ValueTask NextEntryForCurrentStreamAsync(CancellationToken cancellationToken) { if (_entriesForCurrentReadStreamAsync is not null) { @@ -202,6 +199,9 @@ public abstract partial class AbstractReader // Async iterator method protected virtual async IAsyncEnumerable GetEntriesAsync(Stream stream) { +#pragma warning disable VSTHRD111 + await Task.CompletedTask; +#pragma warning restore VSTHRD111 foreach (var entry in GetEntries(stream)) { yield return entry; diff --git a/src/SharpCompress/Readers/AbstractReader.cs b/src/SharpCompress/Readers/AbstractReader.cs index 52410e14..b6a5d32c 100644 --- a/src/SharpCompress/Readers/AbstractReader.cs +++ b/src/SharpCompress/Readers/AbstractReader.cs @@ -179,7 +179,10 @@ public abstract partial class AbstractReader : IReader, IAsyncR s.SkipEntry(); } - public void WriteEntryTo(Stream writableStream) + public void WriteEntryTo(Stream writableStream) => + WriteEntryTo(writableStream, Options.BufferSize); + + private void WriteEntryTo(Stream writableStream, int bufferSize) { if (_wroteCurrentEntry) { @@ -194,15 +197,17 @@ public abstract partial class AbstractReader : IReader, IAsyncR ); } - Write(writableStream); + Write(writableStream, bufferSize); _wroteCurrentEntry = true; } - internal void Write(Stream writeStream) + internal void Write(Stream writeStream) => Write(writeStream, Options.BufferSize); + + internal void Write(Stream writeStream, int bufferSize) { using Stream s = OpenEntryStream(); var sourceStream = WrapWithProgress(s, Entry); - sourceStream.CopyTo(writeStream, Constants.BufferSize); + sourceStream.CopyTo(writeStream, bufferSize); } private Stream WrapWithProgress(Stream source, Entry entry) diff --git a/src/SharpCompress/Readers/IReaderExtensions.cs b/src/SharpCompress/Readers/IReaderExtensions.cs index 74c708a5..93c391d2 100644 --- a/src/SharpCompress/Readers/IReaderExtensions.cs +++ b/src/SharpCompress/Readers/IReaderExtensions.cs @@ -1,5 +1,7 @@ +using System; using System.IO; using SharpCompress.Common; +using SharpCompress.IO; namespace SharpCompress.Readers; @@ -58,9 +60,48 @@ public static class IReaderExtensions options, (x, fm) => { - using var fs = File.Open(destinationFileName, fm); - reader.WriteEntryTo(fs); + using var fs = File.Open(x, fm); + CopyEntryTo(reader, fs, options?.BufferSize ?? Constants.BufferSize); } ); } + + private static void CopyEntryTo(IReader reader, Stream writableStream, int bufferSize) + { + using var entryStream = reader.OpenEntryStream(); + var sourceStream = WrapWithProgress(entryStream, reader.Entry); + sourceStream.CopyTo(writableStream, bufferSize); + } + + private static Stream WrapWithProgress(Stream source, IEntry entry) + { + var progress = entry.Options.Progress; + if (progress is null) + { + return source; + } + + var entryPath = entry.Key ?? string.Empty; + var totalBytes = GetEntrySizeSafe(entry); + return new ProgressReportingStream( + source, + progress, + entryPath, + totalBytes, + leaveOpen: true + ); + } + + private static long? GetEntrySizeSafe(IEntry entry) + { + try + { + var size = entry.Size; + return size >= 0 ? size : null; + } + catch (NotImplementedException) + { + return null; + } + } } diff --git a/tests/SharpCompress.Test/OptionsUsabilityTests.cs b/tests/SharpCompress.Test/OptionsUsabilityTests.cs index 11376f75..b5a997ab 100644 --- a/tests/SharpCompress.Test/OptionsUsabilityTests.cs +++ b/tests/SharpCompress.Test/OptionsUsabilityTests.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; using SharpCompress.Archives; using SharpCompress.Common; @@ -229,6 +230,54 @@ public class OptionsUsabilityTests : TestBase var preserveMetadata = ExtractionOptions.PreserveMetadata; Assert.True(preserveMetadata.PreserveFileTime); Assert.True(preserveMetadata.PreserveAttributes); + + Assert.Equal(Constants.BufferSize, new ExtractionOptions().BufferSize); + } + + [Fact] + public void ArchiveEntry_WriteToFile_Uses_ExtractionOptions_BufferSize() + { + using var source = new TrackingReadStream(new byte[16]); + var entry = new TestArchiveEntry(source); + var destination = Path.Combine(SCRATCH_FILES_PATH, "buffer-size.txt"); + + entry.WriteToFile(destination, new ExtractionOptions { BufferSize = 7 }); + + Assert.Equal(7, source.CopyBufferSize); + } + + [Fact] + public async Task ArchiveEntry_WriteToFileAsync_Uses_ExtractionOptions_BufferSize() + { + using var source = new TrackingReadStream(new byte[16]); + var entry = new TestArchiveEntry(source); + var destination = Path.Combine(SCRATCH_FILES_PATH, "buffer-size-async.txt"); + + await entry.WriteToFileAsync(destination, new ExtractionOptions { BufferSize = 9 }); + + Assert.Equal(9, source.CopyBufferSize); + } + + [Fact] + public void Reader_WriteEntryToFile_Uses_ExtractionOptions_BufferSize() + { + using var reader = new TrackingReader(); + var destination = Path.Combine(SCRATCH_FILES_PATH, "reader-buffer-size.txt"); + + reader.WriteEntryToFile(destination, new ExtractionOptions { BufferSize = 11 }); + + Assert.Equal(11, reader.EntryStreamCopyBufferSize); + } + + [Fact] + public async Task Reader_WriteEntryToFileAsync_Uses_ExtractionOptions_BufferSize() + { + await using var reader = new TrackingReader(); + var destination = Path.Combine(SCRATCH_FILES_PATH, "reader-buffer-size-async.txt"); + + await reader.WriteEntryToFileAsync(destination, new ExtractionOptions { BufferSize = 13 }); + + Assert.Equal(13, reader.EntryStreamCopyBufferSize); } [Fact] @@ -321,4 +370,127 @@ public class OptionsUsabilityTests : TestBase Assert.Null(noPassword.Password); Assert.Equal(1_048_576, noPassword.RewindableBufferSize); } + + private sealed class TrackingReadStream(byte[] data) : MemoryStream(data) + { + public int? CopyBufferSize { get; private set; } + + public override void CopyTo(Stream destination, int bufferSize) + { + CopyBufferSize = bufferSize; + base.CopyTo(destination, bufferSize); + } + + public override Task CopyToAsync( + Stream destination, + int bufferSize, + CancellationToken cancellationToken + ) + { + CopyBufferSize = bufferSize; + return base.CopyToAsync(destination, bufferSize, cancellationToken); + } + } + + private sealed class TestArchiveEntry(Stream source) : IArchiveEntry + { + public CompressionType CompressionType => CompressionType.None; + public DateTime? ArchivedTime => null; + public long CompressedSize => source.Length; + public long Crc => 0; + public DateTime? CreatedTime => null; + public string? Key => "buffer-size.txt"; + public string? LinkTarget => null; + public bool IsDirectory => false; + public bool IsEncrypted => false; + public bool IsSplitAfter => false; + public bool IsSolid => false; + public int VolumeIndexFirst => 0; + public int VolumeIndexLast => 0; + public DateTime? LastAccessedTime => null; + public DateTime? LastModifiedTime => null; + public long Size => source.Length; + public int? Attrib => null; + public SharpCompress.Common.Options.IReaderOptions Options => + ReaderOptions.ForExternalStream; + public bool IsComplete => true; + public IArchive Archive => throw new NotSupportedException(); + + public Stream OpenEntryStream() + { + source.Position = 0; + return source; + } + + public ValueTask OpenEntryStreamAsync(CancellationToken cancellationToken = default) + { + source.Position = 0; + return new ValueTask(source); + } + } + + private sealed class TrackingReader : IReader, IAsyncReader + { + public ArchiveType Type => ArchiveType.Zip; + public TrackingReadStream Source { get; } = new(new byte[100]); + public IEntry Entry => new TestArchiveEntry(Source); + public bool Cancelled => false; + public int? EntryStreamCopyBufferSize { get; private set; } + + public void Dispose() { } + + public ValueTask DisposeAsync() => default; + + public void WriteEntryTo(Stream writableStream) => throw new NotSupportedException(); + + public ValueTask WriteEntryToAsync( + Stream writableStream, + CancellationToken cancellationToken = default + ) => throw new NotSupportedException(); + + public void Cancel() { } + + public bool MoveToNextEntry() => false; + + public ValueTask MoveToNextEntryAsync( + CancellationToken cancellationToken = default + ) => new(false); + + public EntryStream OpenEntryStream() + { + Source.Position = 0; + return new TrackingEntryStream( + this, + Source, + bufferSize => EntryStreamCopyBufferSize = bufferSize + ); + } + + public ValueTask OpenEntryStreamAsync( + CancellationToken cancellationToken = default + ) => new(OpenEntryStream()); + } + + private sealed class TrackingEntryStream( + IReader reader, + Stream stream, + Action copyBufferSize + ) : EntryStream(reader, stream) + { + public override void CopyTo(Stream destination, int bufferSize) + { + copyBufferSize(bufferSize); + base.CopyTo(destination, bufferSize); + } + + public override Task CopyToAsync( + Stream destination, + int bufferSize, + CancellationToken cancellationToken + ) + { + copyBufferSize(bufferSize); + return base.CopyToAsync(destination, bufferSize, cancellationToken); + } + } } From de6e2bfee23be5388965dcac8e2f0d3f379afe1e Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Fri, 12 Jun 2026 14:41:05 +0100 Subject: [PATCH 2/7] Add WriterOptions.BufferSize property --- docs/API.md | 8 +- .../Archives/IArchiveEntryExtensions.cs | 3 +- src/SharpCompress/Common/Constants.cs | 1 + .../Common/Options/IWriterOptions.cs | 5 ++ .../Readers/IAsyncReaderExtensions.cs | 62 ++++++++++++- src/SharpCompress/Utility.Async.cs | 3 +- src/SharpCompress/Utility.cs | 4 +- src/SharpCompress/Writers/GZip/GZipWriter.cs | 2 +- .../Writers/GZip/GZipWriterOptions.cs | 7 ++ .../Writers/SevenZip/SevenZipWriterOptions.cs | 7 ++ .../Writers/Tar/TarWriter.Async.cs | 7 +- src/SharpCompress/Writers/Tar/TarWriter.cs | 6 +- .../Writers/Tar/TarWriterOptions.cs | 7 ++ src/SharpCompress/Writers/WriterOptions.cs | 5 ++ .../Writers/WriterOptionsExtensions.cs | 9 ++ src/SharpCompress/Writers/Zip/ZipWriter.cs | 3 +- .../Writers/Zip/ZipWriterOptions.cs | 7 ++ .../OptionsUsabilityTests.cs | 87 ++++++++++++++++--- tests/SharpCompress.Test/UtilityTests.cs | 6 +- 19 files changed, 210 insertions(+), 29 deletions(-) diff --git a/docs/API.md b/docs/API.md index 0fbcb27f..19edd6ed 100644 --- a/docs/API.md +++ b/docs/API.md @@ -92,7 +92,8 @@ using (var archive = GZipArchive.CreateArchive()) // With fluent options (preferred) var options = WriterOptions.ForZip() .WithCompressionLevel(9) - .WithLeaveStreamOpen(false); + .WithLeaveStreamOpen(false) + .WithBufferSize(131072); using (var archive = ZipArchive.CreateArchive()) { archive.SaveTo("output.zip", options); @@ -102,10 +103,13 @@ using (var archive = ZipArchive.CreateArchive()) var options2 = new WriterOptions(CompressionType.Deflate) { CompressionLevel = 9, - LeaveStreamOpen = false + LeaveStreamOpen = false, + BufferSize = 131072 }; ``` +`WriterOptions.BufferSize` controls stream copy buffers used while writing archive entries. If it is not set, SharpCompress falls back to `Constants.BufferSize`. + --- ## Archive API Methods diff --git a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs index 0f5034c1..0de4b21c 100644 --- a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs +++ b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs @@ -17,7 +17,8 @@ public static class IArchiveEntryExtensions /// /// The stream to write the entry content to. /// Optional progress reporter for tracking extraction progress. - public void WriteTo(Stream streamToWriteTo, IProgress? progress = null) => archiveEntry.WriteTo(streamToWriteTo, null, progress); + public void WriteTo(Stream streamToWriteTo, IProgress? progress = null) => + archiveEntry.WriteTo(streamToWriteTo, null, progress); private void WriteTo( Stream streamToWriteTo, diff --git a/src/SharpCompress/Common/Constants.cs b/src/SharpCompress/Common/Constants.cs index edd81e08..44909ef9 100644 --- a/src/SharpCompress/Common/Constants.cs +++ b/src/SharpCompress/Common/Constants.cs @@ -8,6 +8,7 @@ public static class Constants /// The default buffer size for stream operations, matching .NET's Stream.CopyTo default of 81920 bytes. /// This can be modified globally at runtime. /// + // TODO: Revisit remaining non-extraction usages after extraction buffering moves to ExtractionOptions. public static int BufferSize { get; set; } = 81920; /// diff --git a/src/SharpCompress/Common/Options/IWriterOptions.cs b/src/SharpCompress/Common/Options/IWriterOptions.cs index a7a1d719..37fbb21e 100644 --- a/src/SharpCompress/Common/Options/IWriterOptions.cs +++ b/src/SharpCompress/Common/Options/IWriterOptions.cs @@ -19,6 +19,11 @@ public interface IWriterOptions : IStreamOptions, IEncodingOptions, IProgressOpt /// int CompressionLevel { get; set; } + /// + /// Buffer size for writer stream copy operations. + /// + int BufferSize { get; set; } + /// /// Registry of compression providers. /// Defaults to but can be replaced with custom providers, such as diff --git a/src/SharpCompress/Readers/IAsyncReaderExtensions.cs b/src/SharpCompress/Readers/IAsyncReaderExtensions.cs index 3c1d0408..a982982c 100644 --- a/src/SharpCompress/Readers/IAsyncReaderExtensions.cs +++ b/src/SharpCompress/Readers/IAsyncReaderExtensions.cs @@ -1,7 +1,9 @@ +using System; using System.IO; using System.Threading; using System.Threading.Tasks; using SharpCompress.Common; +using SharpCompress.IO; namespace SharpCompress.Readers; @@ -42,7 +44,8 @@ public static class IAsyncReaderExtensions async (x, fm, ct) => { using var fs = File.Open(x, fm); - await reader.WriteEntryToAsync(fs, ct).ConfigureAwait(false); + await CopyEntryToAsync(reader, fs, options?.BufferSize, ct) + .ConfigureAwait(false); }, cancellationToken ) @@ -77,7 +80,8 @@ public static class IAsyncReaderExtensions async (x, fm, ct) => { using var fs = File.Open(x, fm); - await reader.WriteEntryToAsync(fs, ct).ConfigureAwait(false); + await CopyEntryToAsync(reader, fs, options?.BufferSize, ct) + .ConfigureAwait(false); }, cancellationToken ) @@ -92,4 +96,58 @@ public static class IAsyncReaderExtensions .WriteEntryToAsync(destinationFileInfo.FullName, options, cancellationToken) .ConfigureAwait(false); } + + private static async ValueTask CopyEntryToAsync( + IAsyncReader reader, + Stream writableStream, + int? bufferSize, + CancellationToken cancellationToken + ) + { +#if LEGACY_DOTNET + using var entryStream = await reader + .OpenEntryStreamAsync(cancellationToken) + .ConfigureAwait(false); +#else + await using var entryStream = await reader + .OpenEntryStreamAsync(cancellationToken) + .ConfigureAwait(false); +#endif + var sourceStream = WrapWithProgress(entryStream, reader.Entry); + await sourceStream + .CopyToAsync(writableStream, bufferSize ?? Constants.BufferSize, cancellationToken) + .ConfigureAwait(false); + } + + private static Stream WrapWithProgress(Stream source, IEntry entry) + { + var progress = entry.Options.Progress; + if (progress is null) + { + return source; + } + + var entryPath = entry.Key ?? string.Empty; + var totalBytes = GetEntrySizeSafe(entry); + return new ProgressReportingStream( + source, + progress, + entryPath, + totalBytes, + leaveOpen: true + ); + } + + private static long? GetEntrySizeSafe(IEntry entry) + { + try + { + var size = entry.Size; + return size >= 0 ? size : null; + } + catch (NotImplementedException) + { + return null; + } + } } diff --git a/src/SharpCompress/Utility.Async.cs b/src/SharpCompress/Utility.Async.cs index 1c11ccc6..df323d69 100644 --- a/src/SharpCompress/Utility.Async.cs +++ b/src/SharpCompress/Utility.Async.cs @@ -65,13 +65,14 @@ internal static partial class Utility public async ValueTask TransferToAsync( Stream destination, long maxLength, + int? bufferSize = null, CancellationToken cancellationToken = default ) { // Use ReadOnlySubStream to limit reading and leverage framework's CopyToAsync using var limitedStream = new IO.ReadOnlySubStream(source, maxLength); await limitedStream - .CopyToAsync(destination, Constants.BufferSize, cancellationToken) + .CopyToAsync(destination, bufferSize ?? Constants.BufferSize, cancellationToken) .ConfigureAwait(false); return limitedStream.Position; } diff --git a/src/SharpCompress/Utility.cs b/src/SharpCompress/Utility.cs index 017bc965..c03c3643 100644 --- a/src/SharpCompress/Utility.cs +++ b/src/SharpCompress/Utility.cs @@ -150,11 +150,11 @@ internal static partial class Utility extension(Stream source) { - public long TransferTo(Stream destination, long maxLength) + public long TransferTo(Stream destination, long maxLength, int? bufferSize) { // Use ReadOnlySubStream to limit reading and leverage framework's CopyTo using var limitedStream = new IO.ReadOnlySubStream(source, maxLength); - limitedStream.CopyTo(destination, Constants.BufferSize); + limitedStream.CopyTo(destination, bufferSize ?? Constants.BufferSize); return limitedStream.Position; } diff --git a/src/SharpCompress/Writers/GZip/GZipWriter.cs b/src/SharpCompress/Writers/GZip/GZipWriter.cs index 29c9425c..3c62ff2d 100644 --- a/src/SharpCompress/Writers/GZip/GZipWriter.cs +++ b/src/SharpCompress/Writers/GZip/GZipWriter.cs @@ -86,7 +86,7 @@ public sealed partial class GZipWriter : AbstractWriter } var progressStream = WrapWithProgress(source, filename); - progressStream.CopyTo(OutputStream.NotNull(), Constants.BufferSize); + progressStream.CopyTo(OutputStream.NotNull(), WriterOptions.BufferSize); _wroteToStream = true; } diff --git a/src/SharpCompress/Writers/GZip/GZipWriterOptions.cs b/src/SharpCompress/Writers/GZip/GZipWriterOptions.cs index 42da70e2..ca1b6a9d 100644 --- a/src/SharpCompress/Writers/GZip/GZipWriterOptions.cs +++ b/src/SharpCompress/Writers/GZip/GZipWriterOptions.cs @@ -68,6 +68,11 @@ public sealed record GZipWriterOptions : IWriterOptions /// public IProgress? Progress { get; set; } + /// + /// Buffer size for writer stream copy operations. + /// + public int BufferSize { get; set; } = Constants.BufferSize; + /// /// Registry of compression providers. /// Defaults to but can be replaced with custom implementations, such as @@ -115,6 +120,7 @@ public sealed record GZipWriterOptions : IWriterOptions LeaveStreamOpen = options.LeaveStreamOpen; ArchiveEncoding = options.ArchiveEncoding; Progress = options.Progress; + BufferSize = options.BufferSize; Providers = options.Providers; } @@ -128,6 +134,7 @@ public sealed record GZipWriterOptions : IWriterOptions LeaveStreamOpen = options.LeaveStreamOpen; ArchiveEncoding = options.ArchiveEncoding; Progress = options.Progress; + BufferSize = options.BufferSize; Providers = options.Providers; } } diff --git a/src/SharpCompress/Writers/SevenZip/SevenZipWriterOptions.cs b/src/SharpCompress/Writers/SevenZip/SevenZipWriterOptions.cs index 86fa09ff..6ed26a42 100644 --- a/src/SharpCompress/Writers/SevenZip/SevenZipWriterOptions.cs +++ b/src/SharpCompress/Writers/SevenZip/SevenZipWriterOptions.cs @@ -57,6 +57,11 @@ public sealed record SevenZipWriterOptions : IWriterOptions /// public IProgress? Progress { get; set; } + /// + /// Buffer size for writer stream copy operations. + /// + public int BufferSize { get; set; } = Constants.BufferSize; + /// /// Registry of compression providers. /// Defaults to but can be replaced with custom implementations. @@ -103,6 +108,7 @@ public sealed record SevenZipWriterOptions : IWriterOptions LeaveStreamOpen = options.LeaveStreamOpen; ArchiveEncoding = options.ArchiveEncoding; Progress = options.Progress; + BufferSize = options.BufferSize; Providers = options.Providers; } @@ -117,6 +123,7 @@ public sealed record SevenZipWriterOptions : IWriterOptions LeaveStreamOpen = options.LeaveStreamOpen; ArchiveEncoding = options.ArchiveEncoding; Progress = options.Progress; + BufferSize = options.BufferSize; Providers = options.Providers; } diff --git a/src/SharpCompress/Writers/Tar/TarWriter.Async.cs b/src/SharpCompress/Writers/Tar/TarWriter.Async.cs index 7f4b3886..9244608f 100644 --- a/src/SharpCompress/Writers/Tar/TarWriter.Async.cs +++ b/src/SharpCompress/Writers/Tar/TarWriter.Async.cs @@ -104,7 +104,12 @@ public partial class TarWriter await header.WriteAsync(OutputStream.NotNull(), cancellationToken).ConfigureAwait(false); var progressStream = WrapWithProgress(source, filename); var written = await progressStream - .TransferToAsync(OutputStream.NotNull(), realSize, cancellationToken) + .TransferToAsync( + OutputStream.NotNull(), + realSize, + WriterOptions.BufferSize, + cancellationToken + ) .ConfigureAwait(false); await PadTo512Async(written, cancellationToken).ConfigureAwait(false); } diff --git a/src/SharpCompress/Writers/Tar/TarWriter.cs b/src/SharpCompress/Writers/Tar/TarWriter.cs index 4ed20335..8136cc58 100644 --- a/src/SharpCompress/Writers/Tar/TarWriter.cs +++ b/src/SharpCompress/Writers/Tar/TarWriter.cs @@ -133,7 +133,11 @@ public partial class TarWriter : AbstractWriter header.Size = realSize; header.Write(OutputStream.NotNull()); var progressStream = WrapWithProgress(source, filename); - size = progressStream.TransferTo(OutputStream.NotNull(), realSize); + size = progressStream.TransferTo( + OutputStream.NotNull(), + realSize, + WriterOptions.BufferSize + ); PadTo512(size.Value); } diff --git a/src/SharpCompress/Writers/Tar/TarWriterOptions.cs b/src/SharpCompress/Writers/Tar/TarWriterOptions.cs index 004d4643..d7aaf22b 100755 --- a/src/SharpCompress/Writers/Tar/TarWriterOptions.cs +++ b/src/SharpCompress/Writers/Tar/TarWriterOptions.cs @@ -44,6 +44,11 @@ public sealed record TarWriterOptions : IWriterOptions /// public IProgress? Progress { get; set; } + /// + /// Buffer size for writer stream copy operations. + /// + public int BufferSize { get; set; } = Constants.BufferSize; + /// /// Registry of compression providers. /// Defaults to but can be replaced with custom implementations. @@ -104,6 +109,7 @@ public sealed record TarWriterOptions : IWriterOptions LeaveStreamOpen = options.LeaveStreamOpen; ArchiveEncoding = options.ArchiveEncoding; Progress = options.Progress; + BufferSize = options.BufferSize; Providers = options.Providers; } @@ -118,6 +124,7 @@ public sealed record TarWriterOptions : IWriterOptions LeaveStreamOpen = options.LeaveStreamOpen; ArchiveEncoding = options.ArchiveEncoding; Progress = options.Progress; + BufferSize = options.BufferSize; Providers = options.Providers; } diff --git a/src/SharpCompress/Writers/WriterOptions.cs b/src/SharpCompress/Writers/WriterOptions.cs index e4dd4ac0..d70ea6aa 100644 --- a/src/SharpCompress/Writers/WriterOptions.cs +++ b/src/SharpCompress/Writers/WriterOptions.cs @@ -56,6 +56,11 @@ public sealed record WriterOptions : IWriterOptions /// public IProgress? Progress { get; set; } + /// + /// Buffer size for writer stream copy operations. + /// + public int BufferSize { get; set; } = Constants.BufferSize; + /// /// Registry of compression providers. /// Defaults to but can be replaced with custom implementations, such as diff --git a/src/SharpCompress/Writers/WriterOptionsExtensions.cs b/src/SharpCompress/Writers/WriterOptionsExtensions.cs index 964d9187..2aca567a 100644 --- a/src/SharpCompress/Writers/WriterOptionsExtensions.cs +++ b/src/SharpCompress/Writers/WriterOptionsExtensions.cs @@ -53,6 +53,15 @@ public static class WriterOptionsExtensions ), }; + /// + /// Creates a copy with the specified buffer size. + /// + public static WriterOptions WithBufferSize(this WriterOptions options, int bufferSize) => + options with + { + BufferSize = bufferSize, + }; + /// /// Creates a copy with the specified compression level. /// diff --git a/src/SharpCompress/Writers/Zip/ZipWriter.cs b/src/SharpCompress/Writers/Zip/ZipWriter.cs index 51695341..128cb9d7 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriter.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriter.cs @@ -16,7 +16,6 @@ using SharpCompress.Compressors.PPMd; using SharpCompress.Compressors.ZStandard; using SharpCompress.IO; using SharpCompress.Providers; -using Constants = SharpCompress.Common.Constants; namespace SharpCompress.Writers.Zip; @@ -89,7 +88,7 @@ public partial class ZipWriter : AbstractWriter { using var output = WriteToStream(entryPath, zipWriterEntryOptions); var progressStream = WrapWithProgress(source, entryPath); - progressStream.CopyTo(output, Constants.BufferSize); + progressStream.CopyTo(output, WriterOptions.BufferSize); } public Stream WriteToStream(string entryPath, ZipWriterEntryOptions options) diff --git a/src/SharpCompress/Writers/Zip/ZipWriterOptions.cs b/src/SharpCompress/Writers/Zip/ZipWriterOptions.cs index e3bf2834..73c92ee6 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriterOptions.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriterOptions.cs @@ -61,6 +61,11 @@ public sealed record ZipWriterOptions : IWriterOptions /// public IProgress? Progress { get; set; } + /// + /// Buffer size for writer stream copy operations. + /// + public int BufferSize { get; set; } = Constants.BufferSize; + /// /// Registry of compression providers. /// Defaults to but can be replaced with custom implementations. @@ -128,6 +133,7 @@ public sealed record ZipWriterOptions : IWriterOptions LeaveStreamOpen = options.LeaveStreamOpen; ArchiveEncoding = options.ArchiveEncoding; Progress = options.Progress; + BufferSize = options.BufferSize; Providers = options.Providers; } @@ -141,6 +147,7 @@ public sealed record ZipWriterOptions : IWriterOptions LeaveStreamOpen = options.LeaveStreamOpen; ArchiveEncoding = options.ArchiveEncoding; Progress = options.Progress; + BufferSize = options.BufferSize; Providers = options.Providers; } diff --git a/tests/SharpCompress.Test/OptionsUsabilityTests.cs b/tests/SharpCompress.Test/OptionsUsabilityTests.cs index b5a997ab..85aebb73 100644 --- a/tests/SharpCompress.Test/OptionsUsabilityTests.cs +++ b/tests/SharpCompress.Test/OptionsUsabilityTests.cs @@ -11,6 +11,7 @@ using SharpCompress.Readers; using SharpCompress.Test.Mocks; using SharpCompress.Writers; using SharpCompress.Writers.GZip; +using SharpCompress.Writers.Tar; using SharpCompress.Writers.Zip; using Xunit; @@ -133,11 +134,16 @@ public class OptionsUsabilityTests : TestBase [Fact] public void WriterOptions_Fluent_Methods_Modify_Correctly() { - var options = WriterOptions.ForZip().WithLeaveStreamOpen(false).WithCompressionLevel(9); + var options = WriterOptions + .ForZip() + .WithLeaveStreamOpen(false) + .WithCompressionLevel(9) + .WithBufferSize(65536); Assert.Equal(CompressionType.Deflate, options.CompressionType); Assert.Equal(9, options.CompressionLevel); Assert.False(options.LeaveStreamOpen); + Assert.Equal(65536, options.BufferSize); } [Fact] @@ -161,6 +167,73 @@ public class OptionsUsabilityTests : TestBase Assert.Equal(factoryApproach.LeaveStreamOpen, constructorApproach.LeaveStreamOpen); } + [Fact] + public void WriterOptions_Default_BufferSize_Uses_Constants_BufferSize() + { + Assert.Equal(Constants.BufferSize, WriterOptions.ForZip().BufferSize); + Assert.Equal( + Constants.BufferSize, + new ZipWriterOptions(CompressionType.Deflate).BufferSize + ); + Assert.Equal( + Constants.BufferSize, + new TarWriterOptions(CompressionType.None, true).BufferSize + ); + Assert.Equal(Constants.BufferSize, new GZipWriterOptions().BufferSize); + } + + [Fact] + public void Format_WriterOptions_Copy_BufferSize() + { + var options = WriterOptions.ForZip().WithBufferSize(12345); + + Assert.Equal(12345, new ZipWriterOptions(options).BufferSize); + Assert.Equal(12345, new TarWriterOptions(options).BufferSize); + Assert.Equal(12345, new GZipWriterOptions(options).BufferSize); + } + + [Fact] + public void ZipWriter_Uses_WriterOptions_BufferSize() + { + using var source = new TrackingReadStream(new byte[100]); + using var destination = new MemoryStream(); + using var writer = new ZipWriter( + destination, + new ZipWriterOptions(CompressionType.None) { BufferSize = 17 } + ); + + writer.Write("buffer-size.txt", source, DateTime.Now); + + Assert.Equal(17, source.CopyBufferSize); + } + + [Fact] + public void GZipWriter_Uses_WriterOptions_BufferSize() + { + using var source = new TrackingReadStream(new byte[100]); + using var destination = new MemoryStream(); + using var writer = new GZipWriter(destination, new GZipWriterOptions { BufferSize = 19 }); + + writer.Write("buffer-size.txt", source, DateTime.Now); + + Assert.Equal(19, source.CopyBufferSize); + } + + [Fact] + public void TarWriter_Uses_WriterOptions_BufferSize() + { + using var source = new TrackingReadStream(new byte[100]); + using var destination = new MemoryStream(); + using var writer = new TarWriter( + destination, + new TarWriterOptions(CompressionType.None, true) { BufferSize = 0 } + ); + + Assert.Throws(() => + writer.Write("buffer-size.txt", source, DateTime.Now) + ); + } + [Fact] public void ReaderOptions_Fluent_Methods_Modify_Correctly() { @@ -375,12 +448,6 @@ public class OptionsUsabilityTests : TestBase { public int? CopyBufferSize { get; private set; } - public override void CopyTo(Stream destination, int bufferSize) - { - CopyBufferSize = bufferSize; - base.CopyTo(destination, bufferSize); - } - public override Task CopyToAsync( Stream destination, int bufferSize, @@ -477,12 +544,6 @@ public class OptionsUsabilityTests : TestBase Action copyBufferSize ) : EntryStream(reader, stream) { - public override void CopyTo(Stream destination, int bufferSize) - { - copyBufferSize(bufferSize); - base.CopyTo(destination, bufferSize); - } - public override Task CopyToAsync( Stream destination, int bufferSize, diff --git a/tests/SharpCompress.Test/UtilityTests.cs b/tests/SharpCompress.Test/UtilityTests.cs index 20d46e6b..4dbe2f83 100644 --- a/tests/SharpCompress.Test/UtilityTests.cs +++ b/tests/SharpCompress.Test/UtilityTests.cs @@ -579,7 +579,7 @@ public class UtilityTests using var source = new MemoryStream(sourceData); using var destination = new MemoryStream(); - var transferred = source.TransferTo(destination, 5); + var transferred = source.TransferTo(destination, 5, null); Assert.Equal(5, transferred); Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, destination.ToArray()); @@ -592,7 +592,7 @@ public class UtilityTests using var source = new MemoryStream(sourceData); using var destination = new MemoryStream(); - var transferred = source.TransferTo(destination, 100); + var transferred = source.TransferTo(destination, 100, null); Assert.Equal(3, transferred); Assert.Equal(sourceData, destination.ToArray()); @@ -604,7 +604,7 @@ public class UtilityTests using var source = new MemoryStream(); using var destination = new MemoryStream(); - var transferred = source.TransferTo(destination, 100); + var transferred = source.TransferTo(destination, 100, null); Assert.Equal(0, transferred); Assert.Empty(destination.ToArray()); From 21b9c33c95b4b95de4a468c4a5ad286576fff0b3 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Fri, 12 Jun 2026 14:51:56 +0100 Subject: [PATCH 3/7] Fix tests --- .../OptionsUsabilityTests.cs | 74 ++++++++----------- 1 file changed, 32 insertions(+), 42 deletions(-) diff --git a/tests/SharpCompress.Test/OptionsUsabilityTests.cs b/tests/SharpCompress.Test/OptionsUsabilityTests.cs index 85aebb73..8b08b265 100644 --- a/tests/SharpCompress.Test/OptionsUsabilityTests.cs +++ b/tests/SharpCompress.Test/OptionsUsabilityTests.cs @@ -1,3 +1,4 @@ +#if !LEGACY_DOTNET using System; using System.IO; using System.Linq; @@ -220,9 +221,9 @@ public class OptionsUsabilityTests : TestBase } [Fact] - public void TarWriter_Uses_WriterOptions_BufferSize() + public void TarWriter_Uses_WriterOptions_BufferSize_ForTransfer() { - using var source = new TrackingReadStream(new byte[100]); + using var source = new MemoryStream(new byte[100]); using var destination = new MemoryStream(); using var writer = new TarWriter( destination, @@ -307,30 +308,6 @@ public class OptionsUsabilityTests : TestBase Assert.Equal(Constants.BufferSize, new ExtractionOptions().BufferSize); } - [Fact] - public void ArchiveEntry_WriteToFile_Uses_ExtractionOptions_BufferSize() - { - using var source = new TrackingReadStream(new byte[16]); - var entry = new TestArchiveEntry(source); - var destination = Path.Combine(SCRATCH_FILES_PATH, "buffer-size.txt"); - - entry.WriteToFile(destination, new ExtractionOptions { BufferSize = 7 }); - - Assert.Equal(7, source.CopyBufferSize); - } - - [Fact] - public async Task ArchiveEntry_WriteToFileAsync_Uses_ExtractionOptions_BufferSize() - { - using var source = new TrackingReadStream(new byte[16]); - var entry = new TestArchiveEntry(source); - var destination = Path.Combine(SCRATCH_FILES_PATH, "buffer-size-async.txt"); - - await entry.WriteToFileAsync(destination, new ExtractionOptions { BufferSize = 9 }); - - Assert.Equal(9, source.CopyBufferSize); - } - [Fact] public void Reader_WriteEntryToFile_Uses_ExtractionOptions_BufferSize() { @@ -444,21 +421,6 @@ public class OptionsUsabilityTests : TestBase Assert.Equal(1_048_576, noPassword.RewindableBufferSize); } - private sealed class TrackingReadStream(byte[] data) : MemoryStream(data) - { - public int? CopyBufferSize { get; private set; } - - public override Task CopyToAsync( - Stream destination, - int bufferSize, - CancellationToken cancellationToken - ) - { - CopyBufferSize = bufferSize; - return base.CopyToAsync(destination, bufferSize, cancellationToken); - } - } - private sealed class TestArchiveEntry(Stream source) : IArchiveEntry { public CompressionType CompressionType => CompressionType.None; @@ -496,10 +458,31 @@ public class OptionsUsabilityTests : TestBase } } + private sealed class TrackingReadStream(byte[] data) : MemoryStream(data) + { + public int? CopyBufferSize { get; private set; } + + public override void CopyTo(Stream destination, int bufferSize) + { + CopyBufferSize = bufferSize; + base.CopyTo(destination, bufferSize); + } + + public override Task CopyToAsync( + Stream destination, + int bufferSize, + CancellationToken cancellationToken + ) + { + CopyBufferSize = bufferSize; + return base.CopyToAsync(destination, bufferSize, cancellationToken); + } + } + private sealed class TrackingReader : IReader, IAsyncReader { public ArchiveType Type => ArchiveType.Zip; - public TrackingReadStream Source { get; } = new(new byte[100]); + public MemoryStream Source { get; } = new(new byte[100]); public IEntry Entry => new TestArchiveEntry(Source); public bool Cancelled => false; public int? EntryStreamCopyBufferSize { get; private set; } @@ -544,6 +527,12 @@ public class OptionsUsabilityTests : TestBase Action copyBufferSize ) : EntryStream(reader, stream) { + public override void CopyTo(Stream destination, int bufferSize) + { + copyBufferSize(bufferSize); + base.CopyTo(destination, bufferSize); + } + public override Task CopyToAsync( Stream destination, int bufferSize, @@ -555,3 +544,4 @@ public class OptionsUsabilityTests : TestBase } } } +#endif From 0fdd9e6fc66d62d27b5853c58f6a200e910bf1be Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Fri, 12 Jun 2026 14:54:14 +0100 Subject: [PATCH 4/7] update csharpier --- .config/dotnet-tools.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 97f37dcc..9e028ef5 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,11 +3,11 @@ "isRoot": true, "tools": { "csharpier": { - "version": "1.2.6", + "version": "1.3.0", "commands": [ "csharpier" ], "rollForward": false } } -} \ No newline at end of file +} From 61ae3f21c782f007ca5505d5eaaac84e4514aaaa Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Fri, 12 Jun 2026 15:01:10 +0100 Subject: [PATCH 5/7] fix package lock --- src/SharpCompress/packages.lock.json | 12 ++++++------ tests/SharpCompress.AotSmoke/packages.lock.json | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index 13d0ce05..54571844 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -321,9 +321,9 @@ "net10.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "dVbSXGIFNR5nZcv2tOLoWI+a9T4jtFd77IYjuND+QVe360qWgAF7H0WtoopYhRw/+SgpGUTyrkrh+65+ClNnfw==" + "requested": "[10.0.9, )", + "resolved": "10.0.9", + "contentHash": "4Iw41e2h7I4t70SJcX2GCmbyKJIlA273Cfm9RJMM050/3VBejGAG1KcthP5Z2L6SQcbfbf6BhNWO26+ZG+GzMg==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", @@ -441,9 +441,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.27, )", - "resolved": "8.0.27", - "contentHash": "rQi9TxifHRnXP7lVRZH05DxD2/XGbJp12q0ozcbrlBlBnyyzssFTH/2vLhtKWUp2CT1qVscTrcYTFiwTyKPKRg==" + "requested": "[8.0.28, )", + "resolved": "8.0.28", + "contentHash": "XMqgVjlLxLqWmEh3c49haXLQwsMNtvo6YscUaqfvEGfg1iA8hnYgkUVq3i9Zu9gKeNKMWiiZKVwZExc/qyEAsQ==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", diff --git a/tests/SharpCompress.AotSmoke/packages.lock.json b/tests/SharpCompress.AotSmoke/packages.lock.json index bfbb6d92..4be8b6d2 100644 --- a/tests/SharpCompress.AotSmoke/packages.lock.json +++ b/tests/SharpCompress.AotSmoke/packages.lock.json @@ -10,9 +10,9 @@ }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "dVbSXGIFNR5nZcv2tOLoWI+a9T4jtFd77IYjuND+QVe360qWgAF7H0WtoopYhRw/+SgpGUTyrkrh+65+ClNnfw==" + "requested": "[10.0.9, )", + "resolved": "10.0.9", + "contentHash": "4Iw41e2h7I4t70SJcX2GCmbyKJIlA273Cfm9RJMM050/3VBejGAG1KcthP5Z2L6SQcbfbf6BhNWO26+ZG+GzMg==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", From 68a74e0f45833114afcf4166c6186ca9700c93b6 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Fri, 12 Jun 2026 15:10:15 +0100 Subject: [PATCH 6/7] Try to make things an exact match --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 50baaf1a..a6dc747f 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { "version": "10.0.300", - "rollForward": "latestPatch" + "rollForward": "disable" } } From 323831cfa76981f5edc9fa24a2adcdf650f2b2f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Jun 2026 14:14:22 +0000 Subject: [PATCH 7/7] chore: update NuGet lock files to match CI SDK versions (ILLink.Tasks 8.0.27, 10.0.8) --- src/SharpCompress/packages.lock.json | 12 ++++++------ tests/SharpCompress.AotSmoke/packages.lock.json | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index 54571844..13d0ce05 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -321,9 +321,9 @@ "net10.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[10.0.9, )", - "resolved": "10.0.9", - "contentHash": "4Iw41e2h7I4t70SJcX2GCmbyKJIlA273Cfm9RJMM050/3VBejGAG1KcthP5Z2L6SQcbfbf6BhNWO26+ZG+GzMg==" + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "dVbSXGIFNR5nZcv2tOLoWI+a9T4jtFd77IYjuND+QVe360qWgAF7H0WtoopYhRw/+SgpGUTyrkrh+65+ClNnfw==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", @@ -441,9 +441,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.28, )", - "resolved": "8.0.28", - "contentHash": "XMqgVjlLxLqWmEh3c49haXLQwsMNtvo6YscUaqfvEGfg1iA8hnYgkUVq3i9Zu9gKeNKMWiiZKVwZExc/qyEAsQ==" + "requested": "[8.0.27, )", + "resolved": "8.0.27", + "contentHash": "rQi9TxifHRnXP7lVRZH05DxD2/XGbJp12q0ozcbrlBlBnyyzssFTH/2vLhtKWUp2CT1qVscTrcYTFiwTyKPKRg==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", diff --git a/tests/SharpCompress.AotSmoke/packages.lock.json b/tests/SharpCompress.AotSmoke/packages.lock.json index 4be8b6d2..bfbb6d92 100644 --- a/tests/SharpCompress.AotSmoke/packages.lock.json +++ b/tests/SharpCompress.AotSmoke/packages.lock.json @@ -10,9 +10,9 @@ }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[10.0.9, )", - "resolved": "10.0.9", - "contentHash": "4Iw41e2h7I4t70SJcX2GCmbyKJIlA273Cfm9RJMM050/3VBejGAG1KcthP5Z2L6SQcbfbf6BhNWO26+ZG+GzMg==" + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "dVbSXGIFNR5nZcv2tOLoWI+a9T4jtFd77IYjuND+QVe360qWgAF7H0WtoopYhRw/+SgpGUTyrkrh+65+ClNnfw==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct",