mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-22 06:54:40 +00:00
gzip using sync generator
This commit is contained in:
@@ -15,9 +15,11 @@ namespace SharpCompress.Archives.GZip;
|
||||
|
||||
public partial class GZipArchive
|
||||
{
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public ValueTask SaveToAsync(string filePath, CancellationToken cancellationToken = default) =>
|
||||
SaveToAsync(new FileInfo(filePath), cancellationToken);
|
||||
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public async ValueTask SaveToAsync(
|
||||
FileInfo fileInfo,
|
||||
CancellationToken cancellationToken = default
|
||||
@@ -28,6 +30,7 @@ public partial class GZipArchive
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
protected override async ValueTask SaveToAsync(
|
||||
Stream stream,
|
||||
GZipWriterOptions options,
|
||||
@@ -54,6 +57,7 @@ public partial class GZipArchive
|
||||
.WriteAsync(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
entry.LastModifiedTime,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
@@ -65,7 +69,12 @@ public partial class GZipArchive
|
||||
.OpenEntryStreamAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
await writer
|
||||
.WriteAsync(entry.Key.NotNull("Entry Key is null"), entryStream, cancellationToken)
|
||||
.WriteAsync(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
entry.LastModifiedTime,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +182,12 @@ public partial class GZipArchive
|
||||
var header = ArrayPool<byte>.Shared.Rent(10);
|
||||
try
|
||||
{
|
||||
await stream.ReadFullyAsync(header, 0, 10, cancellationToken).ConfigureAwait(false);
|
||||
if (
|
||||
!await stream.ReadFullyAsync(header, 0, 10, cancellationToken).ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8)
|
||||
{
|
||||
|
||||
@@ -8,7 +8,6 @@ using SharpCompress.Common.Options;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Readers.GZip;
|
||||
using SharpCompress.Writers;
|
||||
using SharpCompress.Writers.GZip;
|
||||
|
||||
namespace SharpCompress.Archives.GZip;
|
||||
@@ -28,14 +27,6 @@ public partial class GZipArchive
|
||||
return sourceStream.Streams.Select(a => new GZipVolume(a, ReaderOptions, 0));
|
||||
}
|
||||
|
||||
public void SaveTo(string filePath) => SaveTo(new FileInfo(filePath));
|
||||
|
||||
public void SaveTo(FileInfo fileInfo)
|
||||
{
|
||||
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
|
||||
SaveTo(stream, new GZipWriterOptions(CompressionType.GZip));
|
||||
}
|
||||
|
||||
protected override GZipArchiveEntry CreateEntryInternal(
|
||||
string key,
|
||||
Stream source,
|
||||
@@ -54,31 +45,9 @@ public partial class GZipArchive
|
||||
protected override GZipArchiveEntry CreateDirectoryEntry(string key, DateTime? modified) =>
|
||||
throw new NotSupportedException("GZip archives do not support directory entries.");
|
||||
|
||||
protected override void SaveTo(
|
||||
Stream stream,
|
||||
GZipWriterOptions options,
|
||||
IEnumerable<GZipArchiveEntry> oldEntries,
|
||||
IEnumerable<GZipArchiveEntry> newEntries
|
||||
)
|
||||
{
|
||||
if (Entries.Count > 1)
|
||||
{
|
||||
throw new InvalidFormatException("Only one entry is allowed in a GZip Archive");
|
||||
}
|
||||
using var writer = new GZipWriter(stream, options);
|
||||
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
writer.Write(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
entry.LastModifiedTime
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<GZipArchiveEntry> LoadEntries(IEnumerable<GZipVolume> volumes)
|
||||
{
|
||||
// Kept hand-written: the generator cannot map IAsyncEnumerable.SingleAsync to LINQ Single.
|
||||
var stream = volumes.Single().Stream;
|
||||
yield return new GZipArchiveEntry(
|
||||
this,
|
||||
|
||||
@@ -7,23 +7,12 @@ using SharpCompress.Common.Options;
|
||||
|
||||
namespace SharpCompress.Archives.GZip;
|
||||
|
||||
public class GZipArchiveEntry : GZipEntry, IArchiveEntry
|
||||
public partial class GZipArchiveEntry : GZipEntry, IArchiveEntry
|
||||
{
|
||||
internal GZipArchiveEntry(GZipArchive archive, GZipFilePart? part, IReaderOptions readerOptions)
|
||||
: base(part, readerOptions) => Archive = archive;
|
||||
|
||||
public virtual Stream OpenEntryStream()
|
||||
{
|
||||
//this is to reset the stream to be read multiple times
|
||||
var part = (GZipFilePart)Parts.Single();
|
||||
var rawStream = part.GetRawStream();
|
||||
if (rawStream.CanSeek && rawStream.Position != part.EntryStartPosition)
|
||||
{
|
||||
rawStream.Position = part.EntryStartPosition;
|
||||
}
|
||||
return Parts.Single().GetCompressedStream().NotNull();
|
||||
}
|
||||
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public virtual async ValueTask<Stream> OpenEntryStreamAsync(
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ using SharpCompress.Crypto;
|
||||
|
||||
namespace SharpCompress.Common.GZip;
|
||||
|
||||
internal sealed class GZipChecksumValidationStream : Stream
|
||||
internal sealed partial class GZipChecksumValidationStream : Stream
|
||||
{
|
||||
private readonly Stream _source;
|
||||
private readonly Stream _rawStream;
|
||||
@@ -46,23 +46,38 @@ internal sealed class GZipChecksumValidationStream : Stream
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush() => _source.Flush();
|
||||
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public override Task FlushAsync(CancellationToken cancellationToken) =>
|
||||
_source.FlushAsync(cancellationToken);
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public override async Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var read = _source.Read(buffer, offset, count);
|
||||
var read = await _source
|
||||
.ReadAsync(buffer, offset, count, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
|
||||
return read;
|
||||
}
|
||||
|
||||
#if !LEGACY_DOTNET
|
||||
public override int Read(Span<byte> buffer)
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public override async ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var read = _source.Read(buffer);
|
||||
var read = await _source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
#if SYNC_ONLY
|
||||
UpdateAndValidateAtEof(buffer[..read], read);
|
||||
#else
|
||||
UpdateAndValidateAtEof(buffer.Span[..read], read);
|
||||
#endif
|
||||
return read;
|
||||
}
|
||||
#endif
|
||||
@@ -83,32 +98,6 @@ internal sealed class GZipChecksumValidationStream : Stream
|
||||
return value;
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var read = await _source
|
||||
.ReadAsync(buffer, offset, count, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
|
||||
return read;
|
||||
}
|
||||
|
||||
#if !LEGACY_DOTNET
|
||||
public override async ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var read = await _source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
UpdateAndValidateAtEof(buffer.Span[..read], read);
|
||||
return read;
|
||||
}
|
||||
#endif
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace SharpCompress.Common.GZip;
|
||||
|
||||
public partial class GZipEntry
|
||||
{
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
internal static async IAsyncEnumerable<GZipEntry> GetEntriesAsync(
|
||||
Stream stream,
|
||||
ReaderOptions options
|
||||
|
||||
@@ -46,14 +46,4 @@ public partial class GZipEntry : Entry
|
||||
public override bool IsSplitAfter => false;
|
||||
|
||||
internal override IEnumerable<FilePart> Parts => _filePart.Empty();
|
||||
|
||||
internal static IEnumerable<GZipEntry> GetEntries(Stream stream, ReaderOptions options)
|
||||
{
|
||||
yield return new GZipEntry(
|
||||
GZipFilePart.Create(stream, options.ArchiveEncoding, options.Providers),
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
// Async methods moved to GZipEntry.Async.cs
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace SharpCompress.Common.GZip;
|
||||
|
||||
internal sealed partial class GZipFilePart
|
||||
{
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
internal static async ValueTask<GZipFilePart> CreateAsync(
|
||||
Stream stream,
|
||||
IArchiveEncoding archiveEncoding,
|
||||
|
||||
@@ -16,32 +16,6 @@ internal sealed partial class GZipFilePart : FilePart
|
||||
private readonly Stream _stream;
|
||||
private readonly CompressionProviderRegistry _compressionProviders;
|
||||
|
||||
internal static GZipFilePart Create(
|
||||
Stream stream,
|
||||
IArchiveEncoding archiveEncoding,
|
||||
CompressionProviderRegistry compressionProviders
|
||||
)
|
||||
{
|
||||
var part = new GZipFilePart(stream, archiveEncoding, compressionProviders);
|
||||
|
||||
part.ReadAndValidateGzipHeader();
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
var position = stream.Position;
|
||||
stream.Position = stream.Length - 8;
|
||||
part.ReadTrailer();
|
||||
stream.Position = position;
|
||||
part.EntryStartPosition = position;
|
||||
}
|
||||
else
|
||||
{
|
||||
// For non-seekable streams, we can't read the trailer or track position.
|
||||
// Set to 0 since the stream will be read sequentially from its current position.
|
||||
part.EntryStartPosition = 0;
|
||||
}
|
||||
return part;
|
||||
}
|
||||
|
||||
private GZipFilePart(
|
||||
Stream stream,
|
||||
IArchiveEncoding archiveEncoding,
|
||||
@@ -74,6 +48,7 @@ internal sealed partial class GZipFilePart : FilePart
|
||||
|
||||
private void ReadTrailer()
|
||||
{
|
||||
// Keep this sync implementation for its stack allocation on the parser hot path.
|
||||
// Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32
|
||||
Span<byte> trailer = stackalloc byte[8];
|
||||
_stream.ReadFully(trailer);
|
||||
@@ -84,6 +59,7 @@ internal sealed partial class GZipFilePart : FilePart
|
||||
|
||||
private void ReadAndValidateGzipHeader()
|
||||
{
|
||||
// Keep this sync implementation for stackalloc and ReadByte-based header parsing.
|
||||
// read the header on the first read
|
||||
Span<byte> header = stackalloc byte[10];
|
||||
var n = _stream.Read(header);
|
||||
@@ -136,6 +112,7 @@ internal sealed partial class GZipFilePart : FilePart
|
||||
|
||||
private string ReadZeroTerminatedString(Stream stream)
|
||||
{
|
||||
// Keep this sync implementation for its one-byte stack allocation.
|
||||
Span<byte> buf1 = stackalloc byte[1];
|
||||
var list = new List<byte>();
|
||||
var done = false;
|
||||
|
||||
@@ -9,6 +9,10 @@ namespace SharpCompress.Compressors.Deflate;
|
||||
|
||||
public partial class GZipStream
|
||||
{
|
||||
/// <summary>
|
||||
/// Flush the stream.
|
||||
/// </summary>
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public override async Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_disposed)
|
||||
@@ -18,6 +22,38 @@ public partial class GZipStream
|
||||
await BaseStream.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read and decompress data from the source stream.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// With a <c>GZipStream</c>, decompression is done through reading.
|
||||
/// </remarks>
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// byte[] working = new byte[WORKING_BUFFER_SIZE];
|
||||
/// using (System.IO.Stream input = System.IO.File.OpenRead(_CompressedFile))
|
||||
/// {
|
||||
/// using (Stream decompressor= new Ionic.Zlib.GZipStream(input, CompressionMode.Decompress, true))
|
||||
/// {
|
||||
/// using (var output = System.IO.File.Create(_DecompressedFile))
|
||||
/// {
|
||||
/// int n;
|
||||
/// while ((n= decompressor.Read(working, 0, working.Length)) !=0)
|
||||
/// {
|
||||
/// output.Write(working, 0, n);
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="buffer">The buffer into which the decompressed data should be placed.</param>
|
||||
/// <param name="offset">the offset within that data array to put the first byte read.</param>
|
||||
/// <param name="count">the number of bytes to read.</param>
|
||||
/// <returns>the number of bytes actually read</returns>
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public override async Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
@@ -33,6 +69,9 @@ public partial class GZipStream
|
||||
.ReadAsync(buffer, offset, count, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Console.WriteLine("GZipStream::Read(buffer, off({0}), c({1}) = {2}", offset, count, n);
|
||||
// Console.WriteLine( Util.FormatByteArray(buffer, offset, n) );
|
||||
|
||||
if (!_firstReadDone)
|
||||
{
|
||||
_firstReadDone = true;
|
||||
@@ -66,6 +105,29 @@ public partial class GZipStream
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Write data to the stream.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If you wish to use the <c>GZipStream</c> to compress data while writing,
|
||||
/// you can create a <c>GZipStream</c> with <c>CompressionMode.Compress</c>, and a
|
||||
/// writable output stream. Then call <c>Write()</c> on that <c>GZipStream</c>,
|
||||
/// providing uncompressed data as input. The data sent to the output stream
|
||||
/// will be the compressed form of the data written.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// A <c>GZipStream</c> can be used for <c>Read()</c> or <c>Write()</c>, but not
|
||||
/// both. Writing implies compression. Reading implies decompression.
|
||||
/// </para>
|
||||
///
|
||||
/// </remarks>
|
||||
/// <param name="buffer">The buffer holding data to write to the stream.</param>
|
||||
/// <param name="offset">the offset within that data array to find the first byte to write.</param>
|
||||
/// <param name="count">the number of bytes to write.</param>
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public override async Task WriteAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
@@ -79,6 +141,7 @@ public partial class GZipStream
|
||||
}
|
||||
if (BaseStream._streamMode == ZlibBaseStream.StreamMode.Undefined)
|
||||
{
|
||||
//Console.WriteLine("GZipStream: First write");
|
||||
if (BaseStream._wantCompress)
|
||||
{
|
||||
// first write in compression, therefore, emit the GZIP header
|
||||
|
||||
@@ -250,70 +250,6 @@ public partial class GZipStream : Stream
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush the stream.
|
||||
/// </summary>
|
||||
public override void Flush()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("GZipStream");
|
||||
}
|
||||
BaseStream.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read and decompress data from the source stream.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// With a <c>GZipStream</c>, decompression is done through reading.
|
||||
/// </remarks>
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// byte[] working = new byte[WORKING_BUFFER_SIZE];
|
||||
/// using (System.IO.Stream input = System.IO.File.OpenRead(_CompressedFile))
|
||||
/// {
|
||||
/// using (Stream decompressor= new Ionic.Zlib.GZipStream(input, CompressionMode.Decompress, true))
|
||||
/// {
|
||||
/// using (var output = System.IO.File.Create(_DecompressedFile))
|
||||
/// {
|
||||
/// int n;
|
||||
/// while ((n= decompressor.Read(working, 0, working.Length)) !=0)
|
||||
/// {
|
||||
/// output.Write(working, 0, n);
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="buffer">The buffer into which the decompressed data should be placed.</param>
|
||||
/// <param name="offset">the offset within that data array to put the first byte read.</param>
|
||||
/// <param name="count">the number of bytes to read.</param>
|
||||
/// <returns>the number of bytes actually read</returns>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("GZipStream");
|
||||
}
|
||||
var n = BaseStream.Read(buffer, offset, count);
|
||||
|
||||
// Console.WriteLine("GZipStream::Read(buffer, off({0}), c({1}) = {2}", offset, count, n);
|
||||
// Console.WriteLine( Util.FormatByteArray(buffer, offset, n) );
|
||||
|
||||
if (!_firstReadDone)
|
||||
{
|
||||
_firstReadDone = true;
|
||||
FileName = BaseStream._GzipFileName;
|
||||
Comment = BaseStream._GzipComment;
|
||||
LastModified = BaseStream._GzipMtime;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calling this method always throws a <see cref="NotImplementedException"/>.
|
||||
/// </summary>
|
||||
@@ -328,51 +264,6 @@ public partial class GZipStream : Stream
|
||||
/// <param name="value">irrelevant; this method will always throw!</param>
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
/// <summary>
|
||||
/// Write data to the stream.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If you wish to use the <c>GZipStream</c> to compress data while writing,
|
||||
/// you can create a <c>GZipStream</c> with <c>CompressionMode.Compress</c>, and a
|
||||
/// writable output stream. Then call <c>Write()</c> on that <c>GZipStream</c>,
|
||||
/// providing uncompressed data as input. The data sent to the output stream
|
||||
/// will be the compressed form of the data written.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// A <c>GZipStream</c> can be used for <c>Read()</c> or <c>Write()</c>, but not
|
||||
/// both. Writing implies compression. Reading implies decompression.
|
||||
/// </para>
|
||||
///
|
||||
/// </remarks>
|
||||
/// <param name="buffer">The buffer holding data to write to the stream.</param>
|
||||
/// <param name="offset">the offset within that data array to find the first byte to write.</param>
|
||||
/// <param name="count">the number of bytes to write.</param>
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("GZipStream");
|
||||
}
|
||||
if (BaseStream._streamMode == ZlibBaseStream.StreamMode.Undefined)
|
||||
{
|
||||
//Console.WriteLine("GZipStream: First write");
|
||||
if (BaseStream._wantCompress)
|
||||
{
|
||||
// first write in compression, therefore, emit the GZIP header
|
||||
_headerByteCount = EmitHeader();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArchiveOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
BaseStream.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
#endregion Stream methods
|
||||
|
||||
public string? Comment
|
||||
@@ -509,13 +400,7 @@ public partial class GZipStream : Stream
|
||||
return header;
|
||||
}
|
||||
|
||||
private int EmitHeader()
|
||||
{
|
||||
var header = BuildHeader();
|
||||
BaseStream._stream.Write(header, 0, header.Length);
|
||||
return header.Length; // bytes written
|
||||
}
|
||||
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
private async ValueTask<int> EmitHeaderAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var header = BuildHeader();
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace SharpCompress.Providers.Default;
|
||||
/// <summary>
|
||||
/// Provides GZip compression using SharpCompress's internal implementation.
|
||||
/// </summary>
|
||||
public sealed class GZipCompressionProvider : CompressionProviderBase
|
||||
public sealed partial class GZipCompressionProvider : CompressionProviderBase
|
||||
{
|
||||
public override CompressionType CompressionType => CompressionType.GZip;
|
||||
public override bool SupportsCompression => true;
|
||||
@@ -28,23 +28,22 @@ public sealed class GZipCompressionProvider : CompressionProviderBase
|
||||
return new GZipStream(source, CompressionMode.Decompress);
|
||||
}
|
||||
|
||||
public override Stream CreateDecompressStream(Stream source, CompressionContext context)
|
||||
{
|
||||
return new GZipStream(
|
||||
source,
|
||||
CompressionMode.Decompress,
|
||||
CompressionLevel.Default,
|
||||
context.ResolveArchiveEncoding()
|
||||
);
|
||||
}
|
||||
|
||||
public override ValueTask<Stream> CreateDecompressStreamAsync(
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public override async ValueTask<Stream> CreateDecompressStreamAsync(
|
||||
Stream source,
|
||||
CompressionContext context,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new ValueTask<Stream>(CreateDecompressStream(source, context));
|
||||
return await Task.FromResult<Stream>(
|
||||
new GZipStream(
|
||||
source,
|
||||
CompressionMode.Decompress,
|
||||
CompressionLevel.Default,
|
||||
context.ResolveArchiveEncoding()
|
||||
)
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ public partial class GZipReader
|
||||
/// <summary>
|
||||
/// Returns entries asynchronously for streams that only support async reads.
|
||||
/// </summary>
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
protected override IAsyncEnumerable<GZipEntry> GetEntriesAsync(Stream stream) =>
|
||||
GZipEntry.GetEntriesAsync(stream, Options);
|
||||
}
|
||||
|
||||
@@ -11,9 +11,4 @@ public partial class GZipReader : AbstractReader<GZipEntry, GZipVolume>
|
||||
: base(options, ArchiveType.GZip) => Volume = new GZipVolume(stream, options, 0);
|
||||
|
||||
public override GZipVolume Volume { get; }
|
||||
|
||||
protected override IEnumerable<GZipEntry> GetEntries(Stream stream) =>
|
||||
GZipEntry.GetEntries(stream, Options);
|
||||
|
||||
// GetEntriesAsync moved to GZipReader.Async.cs
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Compressors.Deflate;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Writers.GZip;
|
||||
|
||||
public partial class GZipWriter
|
||||
{
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public override async ValueTask WriteAsync(
|
||||
string filename,
|
||||
Stream source,
|
||||
@@ -19,18 +21,28 @@ public partial class GZipWriter
|
||||
{
|
||||
throw new ArgumentException("Can only write a single stream to a GZip file.");
|
||||
}
|
||||
var stream = (GZipStream)OutputStream.NotNull();
|
||||
stream.FileName = filename;
|
||||
stream.LastModified = modificationTime;
|
||||
|
||||
// Custom providers need not expose SharpCompress's internal GZip stream.
|
||||
if (OutputStream is GZipStream gzipStream)
|
||||
{
|
||||
gzipStream.FileName = filename;
|
||||
gzipStream.LastModified = modificationTime;
|
||||
}
|
||||
|
||||
var progressStream = WrapWithProgress(source, filename);
|
||||
#if LEGACY_DOTNET
|
||||
await progressStream.CopyToAsync(stream).ConfigureAwait(false);
|
||||
await progressStream
|
||||
.CopyToAsync(OutputStream.NotNull(), WriterOptions.BufferSize)
|
||||
.ConfigureAwait(false);
|
||||
#else
|
||||
await progressStream.CopyToAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
await progressStream
|
||||
.CopyToAsync(OutputStream.NotNull(), WriterOptions.BufferSize, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
#endif
|
||||
_wroteToStream = true;
|
||||
}
|
||||
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public override ValueTask WriteDirectoryAsync(
|
||||
string directoryName,
|
||||
DateTime? modificationTime,
|
||||
|
||||
@@ -70,26 +70,4 @@ public sealed partial class GZipWriter : AbstractWriter
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA2215
|
||||
|
||||
public override void Write(string filename, Stream source, DateTime? modificationTime)
|
||||
{
|
||||
if (_wroteToStream)
|
||||
{
|
||||
throw new ArgumentException("Can only write a single stream to a GZip file.");
|
||||
}
|
||||
|
||||
// Set metadata on the stream if it's the internal GZipStream
|
||||
if (OutputStream is GZipStream gzipStream)
|
||||
{
|
||||
gzipStream.FileName = filename;
|
||||
gzipStream.LastModified = modificationTime;
|
||||
}
|
||||
|
||||
var progressStream = WrapWithProgress(source, filename);
|
||||
progressStream.CopyTo(OutputStream.NotNull(), WriterOptions.BufferSize);
|
||||
_wroteToStream = true;
|
||||
}
|
||||
|
||||
public override void WriteDirectory(string directoryName, DateTime? modificationTime) =>
|
||||
throw new NotSupportedException("GZip archives do not support directory entries.");
|
||||
}
|
||||
|
||||
@@ -240,4 +240,35 @@ public class GZipArchiveAsyncTests : ArchiveTests
|
||||
|
||||
Assert.True(entryStream.IsDisposed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async ValueTask GZip_Archive_SaveTo_Async_Preserves_Modification_Time()
|
||||
{
|
||||
var modificationTime = new DateTime(2024, 1, 2, 3, 4, 5);
|
||||
using var destination = new MemoryStream();
|
||||
await using (var archive = (GZipArchive)await GZipArchive.CreateAsyncArchive())
|
||||
{
|
||||
await archive.AddEntryAsync(
|
||||
"contents.bin",
|
||||
new MemoryStream(new byte[] { 1, 2, 3, 4 }),
|
||||
size: 4,
|
||||
modified: modificationTime
|
||||
);
|
||||
await archive.SaveToAsync(destination, new GZipWriterOptions());
|
||||
}
|
||||
|
||||
destination.Position = 0;
|
||||
await using var result = await GZipArchive.OpenAsyncArchive(destination);
|
||||
var entry = await result.EntriesAsync.FirstAsync();
|
||||
|
||||
Assert.Equal(modificationTime, entry.LastModifiedTime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async ValueTask GZip_Archive_IsGZipFileAsync_ReturnsFalse_For_Truncated_Header()
|
||||
{
|
||||
using var stream = new MemoryStream(new byte[] { 0x1F, 0x8B, 8 });
|
||||
|
||||
Assert.False(await GZipArchive.IsGZipFileAsync(stream));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Providers;
|
||||
using SharpCompress.Providers.System;
|
||||
using SharpCompress.Test.Mocks;
|
||||
using SharpCompress.Writers;
|
||||
using SharpCompress.Writers.GZip;
|
||||
@@ -91,4 +93,33 @@ public class GZipWriterAsyncTests : WriterTests
|
||||
Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async ValueTask GZip_Writer_Async_With_System_Provider()
|
||||
{
|
||||
var contents = new byte[] { 1, 2, 3, 4 };
|
||||
var providers = CompressionProviderRegistry.Default.With(
|
||||
new SystemGZipCompressionProvider()
|
||||
);
|
||||
using var source = new MemoryStream(contents);
|
||||
using var destination = new MemoryStream();
|
||||
await using (
|
||||
var writer = new GZipWriter(
|
||||
destination,
|
||||
new GZipWriterOptions { LeaveStreamOpen = true, Providers = providers }
|
||||
)
|
||||
)
|
||||
{
|
||||
await writer.WriteAsync("contents.bin", source);
|
||||
}
|
||||
|
||||
destination.Position = 0;
|
||||
using var compressed = new SystemGZipCompressionProvider().CreateDecompressStream(
|
||||
destination
|
||||
);
|
||||
using var actual = new MemoryStream();
|
||||
compressed.CopyTo(actual);
|
||||
|
||||
Assert.Equal(contents, actual.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Writers.GZip;
|
||||
using Xunit;
|
||||
@@ -16,4 +17,15 @@ public class GZipWriterDirectoryTests : TestBase
|
||||
|
||||
Assert.Throws<NotSupportedException>(() => writer.WriteDirectory("test-dir", DateTime.Now));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async ValueTask GZipWriter_WriteDirectoryAsync_ThrowsNotSupportedException()
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using var writer = new GZipWriter(memoryStream, new GZipWriterOptions());
|
||||
|
||||
await Assert.ThrowsAsync<NotSupportedException>(async () =>
|
||||
await writer.WriteDirectoryAsync("test-dir", DateTime.Now)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,6 +220,21 @@ public class OptionsUsabilityTests : TestBase
|
||||
Assert.Equal(19, source.CopyBufferSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GZipWriter_Uses_WriterOptions_BufferSize_Async()
|
||||
{
|
||||
await using var source = new TrackingReadStream(new byte[100]);
|
||||
await using var destination = new MemoryStream();
|
||||
await using var writer = new GZipWriter(
|
||||
destination,
|
||||
new GZipWriterOptions { BufferSize = 23 }
|
||||
);
|
||||
|
||||
await writer.WriteAsync("buffer-size.txt", source, DateTime.Now);
|
||||
|
||||
Assert.Equal(23, source.CopyBufferSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TarWriter_Uses_WriterOptions_BufferSize_ForTransfer()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user