diff --git a/src/ICSharpCode.SharpZipLib/GZip/GzipOutputStream.cs b/src/ICSharpCode.SharpZipLib/GZip/GzipOutputStream.cs
index 0b1a647fe..b15e7a5b4 100644
--- a/src/ICSharpCode.SharpZipLib/GZip/GzipOutputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/GZip/GzipOutputStream.cs
@@ -3,7 +3,9 @@
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using System;
using System.IO;
-using System.Text;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
namespace ICSharpCode.SharpZipLib.GZip
{
@@ -162,6 +164,26 @@ public override void Write(byte[] buffer, int offset, int count)
base.Write(buffer, offset, count);
}
+#if NETSTANDARD2_1_OR_GREATER
+ ///
+ public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+
+ if (state_ == OutputState.Header)
+ {
+ await WriteHeaderAsync();
+ }
+
+ if (state_ != OutputState.Footer)
+ {
+ throw new InvalidOperationException("Write not permitted in current state");
+ }
+
+ crc.Update(new ArraySegment(buffer, offset, count));
+ await base.WriteAsync(buffer, offset, count, cancellationToken);
+ }
+#endif
+
///
/// Writes remaining compressed output data to the output stream
/// and closes it.
@@ -184,6 +206,30 @@ protected override void Dispose(bool disposing)
}
}
}
+
+#if NETSTANDARD2_1_OR_GREATER
+ ///
+ public override async ValueTask DisposeAsync()
+ {
+ try
+ {
+ await FinishAsync(CancellationToken.None);
+ }
+ finally
+ {
+ if (state_ != OutputState.Closed)
+ {
+ state_ = OutputState.Closed;
+ if (IsStreamOwner)
+ {
+ await baseOutputStream_.DisposeAsync();
+ }
+ }
+
+ await base.DisposeAsync();
+ }
+ }
+#endif
///
/// Flushes the stream by ensuring the header is written, and then calling Flush
@@ -218,75 +264,117 @@ public override void Finish()
{
state_ = OutputState.Finished;
base.Finish();
+
+ byte[] gzipFooter = GetFooter();
- var totalin = (uint)(deflater_.TotalIn & 0xffffffff);
- var crcval = (uint)(crc.Value & 0xffffffff);
-
- byte[] gzipFooter;
-
- unchecked
- {
- gzipFooter = new byte[] {
- (byte) crcval, (byte) (crcval >> 8),
- (byte) (crcval >> 16), (byte) (crcval >> 24),
+ baseOutputStream_.Write(gzipFooter, 0, gzipFooter.Length);
+ }
+ }
- (byte) totalin, (byte) (totalin >> 8),
- (byte) (totalin >> 16), (byte) (totalin >> 24)
- };
- }
+#if NETSTANDARD2_1_OR_GREATER
+ ///
+ public override async Task FinishAsync(CancellationToken ct)
+ {
+ // If no data has been written a header should be added.
+ if (state_ == OutputState.Header)
+ {
+ await WriteHeaderAsync();
+ }
- baseOutputStream_.Write(gzipFooter, 0, gzipFooter.Length);
+ if (state_ == OutputState.Footer)
+ {
+ state_ = OutputState.Finished;
+ await base.FinishAsync(ct);
+ await baseOutputStream_.WriteAsync(GetFooter(), ct);
}
}
+#endif
#endregion DeflaterOutputStream overrides
#region Support Routines
- private static string CleanFilename(string path)
- => path.Substring(path.LastIndexOf('/') + 1);
-
- private void WriteHeader()
+ private byte[] GetFooter()
{
- if (state_ == OutputState.Header)
- {
- state_ = OutputState.Footer;
+ var totalin = (uint)(deflater_.TotalIn & 0xffffffff);
+ var crcval = (uint)(crc.Value & 0xffffffff);
- var mod_time = (int)((DateTime.Now.Ticks - new DateTime(1970, 1, 1).Ticks) / 10000000L); // Ticks give back 100ns intervals
- byte[] gzipHeader = {
- // The two magic bytes
- GZipConstants.ID1,
- GZipConstants.ID2,
+ byte[] gzipFooter;
+
+ unchecked
+ {
+ gzipFooter = new [] {
+ (byte) crcval,
+ (byte) (crcval >> 8),
+ (byte) (crcval >> 16),
+ (byte) (crcval >> 24),
+ (byte) totalin,
+ (byte) (totalin >> 8),
+ (byte) (totalin >> 16),
+ (byte) (totalin >> 24),
+ };
+ }
- // The compression type
- GZipConstants.CompressionMethodDeflate,
+ return gzipFooter;
+ }
- // The flags (not set)
- (byte)flags,
+ private byte[] GetHeader()
+ {
+ var modTime = (int)((DateTime.Now.Ticks - new DateTime(1970, 1, 1).Ticks) / 10000000L); // Ticks give back 100ns intervals
+ byte[] gzipHeader = {
+ // The two magic bytes
+ GZipConstants.ID1,
+ GZipConstants.ID2,
- // The modification time
- (byte) mod_time, (byte) (mod_time >> 8),
- (byte) (mod_time >> 16), (byte) (mod_time >> 24),
+ // The compression type
+ GZipConstants.CompressionMethodDeflate,
- // The extra flags
- 0,
+ // The flags (not set)
+ (byte)flags,
- // The OS type (unknown)
- 255
- };
+ // The modification time
+ (byte) modTime, (byte) (modTime >> 8),
+ (byte) (modTime >> 16), (byte) (modTime >> 24),
- baseOutputStream_.Write(gzipHeader, 0, gzipHeader.Length);
+ // The extra flags
+ 0,
- if (flags.HasFlag(GZipFlags.FNAME))
- {
- var fname = GZipConstants.Encoding.GetBytes(fileName);
- baseOutputStream_.Write(fname, 0, fname.Length);
+ // The OS type (unknown)
+ 255
+ };
- // End filename string with a \0
- baseOutputStream_.Write(new byte[] { 0 }, 0, 1);
- }
+ if (!flags.HasFlag(GZipFlags.FNAME))
+ {
+ return gzipHeader;
}
+
+
+ return gzipHeader
+ .Concat(GZipConstants.Encoding.GetBytes(fileName))
+ .Concat(new byte []{0}) // End filename string with a \0
+ .ToArray();
+ }
+
+ private static string CleanFilename(string path)
+ => path.Substring(path.LastIndexOf('/') + 1);
+
+ private void WriteHeader()
+ {
+ if (state_ != OutputState.Header) return;
+ state_ = OutputState.Footer;
+
+ var gzipHeader = GetHeader();
+ baseOutputStream_.Write(gzipHeader, 0, gzipHeader.Length);
+ }
+
+ #if NETSTANDARD2_1_OR_GREATER
+ private async ValueTask WriteHeaderAsync()
+ {
+ if (state_ != OutputState.Header) return;
+ state_ = OutputState.Footer;
+ await baseOutputStream_.WriteAsync(GetHeader());
}
+ #endif
#endregion Support Routines
}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/GZip/GZipTests.cs b/test/ICSharpCode.SharpZipLib.Tests/GZip/GZipTests.cs
index 62be609fc..b7ee7e444 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/GZip/GZipTests.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/GZip/GZipTests.cs
@@ -4,6 +4,8 @@
using System;
using System.IO;
using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
namespace ICSharpCode.SharpZipLib.Tests.GZip
{
@@ -428,7 +430,50 @@ public void SmallBufferDecompression()
}
}
+
+#if NETCOREAPP3_1_OR_GREATER
+ [Test]
+ [Category("GZip")]
+ [Category("Async")]
+ public async Task SmallBufferDecompressionAsync()
+ {
+ var outputBufferSize = 100000;
+ var inputBufferSize = outputBufferSize * 4;
+ var inputBuffer = Utils.GetDummyBytes(inputBufferSize, seed: 0);
+
+ var outputBuffer = new byte[outputBufferSize];
+ await using var msGzip = new MemoryStream();
+ await using (var gzos = new GZipOutputStream(msGzip){IsStreamOwner = false})
+ {
+ await gzos.WriteAsync(inputBuffer, 0, inputBuffer.Length);
+ }
+
+ msGzip.Seek(0, SeekOrigin.Begin);
+
+
+ using (var gzis = new GZipInputStream(msGzip))
+ await using (var msRaw = new MemoryStream())
+ {
+
+ int readOut;
+ while ((readOut = await gzis.ReadAsync(outputBuffer, 0, outputBuffer.Length)) > 0)
+ {
+ await msRaw.WriteAsync(outputBuffer, 0, readOut);
+ }
+
+ var resultBuffer = msRaw.ToArray();
+
+ for (var i = 0; i < resultBuffer.Length; i++)
+ {
+ Assert.AreEqual(inputBuffer[i], resultBuffer[i]);
+ }
+
+
+ }
+ }
+#endif
+
///
/// Should gracefully handle reading from a stream that becomes unreadable after
/// all of the data has been read.