Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "10.0.300",
"rollForward": "latestPatch"
"rollForward": "disable"
}
}
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