Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"isRoot": true,
"tools": {
"csharpier": {
"version": "1.2.6",
"version": "1.3.0",
"commands": [
"csharpier"
],
"rollForward": false
}
}
}
}
18 changes: 16 additions & 2 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -315,6 +319,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
Expand All @@ -334,6 +341,13 @@ var options = new ReaderOptions
BufferSize = 81920,
RewindableBufferSize = 1_048_576,
};

var extractionOptions = new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = true,
BufferSize = 131072,
};
```

### WriterOptions
Expand Down
7 changes: 6 additions & 1 deletion docs/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
);
}
```
Expand Down
39 changes: 31 additions & 8 deletions src/SharpCompress/Archives/IArchiveEntryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ public static class IArchiveEntryExtensions
/// </summary>
/// <param name="streamToWriteTo">The stream to write the entry content to.</param>
/// <param name="progress">Optional progress reporter for tracking extraction progress.</param>
public void WriteTo(Stream streamToWriteTo, IProgress<ProgressReport>? progress = null)
public void WriteTo(Stream streamToWriteTo, IProgress<ProgressReport>? progress = null) =>
archiveEntry.WriteTo(streamToWriteTo, null, progress);

private void WriteTo(
Stream streamToWriteTo,
int? bufferSize,
IProgress<ProgressReport>? progress = null
)
{
if (archiveEntry.IsDirectory)
{
Expand All @@ -26,7 +33,7 @@ public void WriteTo(Stream streamToWriteTo, IProgress<ProgressReport>? progress

using var entryStream = archiveEntry.OpenEntryStream();
var sourceStream = WrapWithProgress(entryStream, archiveEntry, progress);
sourceStream.CopyTo(streamToWriteTo, Constants.BufferSize);
sourceStream.CopyTo(streamToWriteTo, bufferSize ?? Constants.BufferSize);
}

/// <summary>
Expand All @@ -40,6 +47,18 @@ public async ValueTask WriteToAsync(
IProgress<ProgressReport>? progress = null,
CancellationToken cancellationToken = default
)
{
await archiveEntry
.WriteToAsync(streamToWriteTo, Constants.BufferSize, progress, cancellationToken)
.ConfigureAwait(false);
}

private async ValueTask WriteToAsync(
Stream streamToWriteTo,
int? bufferSize,
IProgress<ProgressReport>? progress = null,
CancellationToken cancellationToken = default
)
{
if (archiveEntry.IsDirectory)
{
Expand All @@ -57,7 +76,7 @@ public async ValueTask WriteToAsync(
#endif
var sourceStream = WrapWithProgress(entryStream, archiveEntry, progress);
await sourceStream
.CopyToAsync(streamToWriteTo, Constants.BufferSize, cancellationToken)
.CopyToAsync(streamToWriteTo, bufferSize ?? Constants.BufferSize, cancellationToken)
.ConfigureAwait(false);
}
}
Expand Down Expand Up @@ -133,16 +152,18 @@ await entry.WriteToFileAsync(path, options, ct).ConfigureAwait(false),
/// <summary>
/// Extract to specific file
/// </summary>
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);
}
);
}

/// <summary>
/// Extract to specific file asynchronously
Expand All @@ -158,8 +179,10 @@ await entry
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
)
Expand Down
1 change: 1 addition & 0 deletions src/SharpCompress/Common/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
// TODO: Revisit remaining non-extraction usages after extraction buffering moves to ExtractionOptions.
public static int BufferSize { get; set; } = 81920;

/// <summary>
Expand Down
5 changes: 5 additions & 0 deletions src/SharpCompress/Common/ExtractionOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ public sealed record ExtractionOptions : IExtractionOptions
/// </summary>
public bool PreserveAttributes { get; set; }

/// <summary>
/// Buffer size for extraction stream copy operations.
/// </summary>
public int BufferSize { get; set; } = Constants.BufferSize;

/// <summary>
/// Delegate for writing symbolic links to disk.
/// The first parameter is the source path (where the symlink is created).
Expand Down
5 changes: 5 additions & 0 deletions src/SharpCompress/Common/Options/IExtractionOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public interface IExtractionOptions
/// </summary>
bool PreserveAttributes { get; set; }

/// <summary>
/// Buffer size for extraction stream copy operations.
/// </summary>
int BufferSize { get; set; }

/// <summary>
/// Delegate for writing symbolic links to disk.
/// The first parameter is the source path (where the symlink is created).
Expand Down
5 changes: 5 additions & 0 deletions src/SharpCompress/Common/Options/IWriterOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ public interface IWriterOptions : IStreamOptions, IEncodingOptions, IProgressOpt
/// </summary>
int CompressionLevel { get; set; }

/// <summary>
/// Buffer size for writer stream copy operations.
/// </summary>
int BufferSize { get; set; }

/// <summary>
/// Registry of compression providers.
/// Defaults to <see cref="CompressionProviderRegistry.Default" /> but can be replaced with custom providers, such as
Expand Down
14 changes: 7 additions & 7 deletions src/SharpCompress/Readers/AbstractReader.Async.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.IO;

namespace SharpCompress.Readers;

Expand Down Expand Up @@ -138,19 +137,19 @@ public async ValueTask WriteEntryToAsync(
_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
}
Expand Down Expand Up @@ -187,9 +186,7 @@ internal virtual ValueTask<bool> NextEntryForCurrentStreamAsync() =>
/// <summary>
/// Moves the current async enumerator to the next entry.
/// </summary>
internal virtual ValueTask<bool> NextEntryForCurrentStreamAsync(
CancellationToken cancellationToken
)
private ValueTask<bool> NextEntryForCurrentStreamAsync(CancellationToken cancellationToken)
{
if (_entriesForCurrentReadStreamAsync is not null)
{
Expand All @@ -202,6 +199,9 @@ CancellationToken cancellationToken
// Async iterator method
protected virtual async IAsyncEnumerable<TEntry> GetEntriesAsync(Stream stream)
{
#pragma warning disable VSTHRD111
await Task.CompletedTask;
#pragma warning restore VSTHRD111
foreach (var entry in GetEntries(stream))
{
yield return entry;
Expand Down
13 changes: 9 additions & 4 deletions src/SharpCompress/Readers/AbstractReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ private void Skip()
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)
{
Expand All @@ -194,15 +197,17 @@ public void WriteEntryTo(Stream writableStream)
);
}

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)
Expand Down
62 changes: 60 additions & 2 deletions src/SharpCompress/Readers/IAsyncReaderExtensions.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -42,7 +44,8 @@ await reader
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
)
Expand Down Expand Up @@ -77,7 +80,8 @@ await reader
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
)
Expand All @@ -92,4 +96,58 @@ await reader
.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;
}
}
}
Loading
Loading