diff --git a/src/ICSharpCode.SharpZipLib/GZip/GzipInputStream.cs b/src/ICSharpCode.SharpZipLib/GZip/GzipInputStream.cs
index 20a4ded17..db5aef2da 100644
--- a/src/ICSharpCode.SharpZipLib/GZip/GzipInputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/GZip/GzipInputStream.cs
@@ -334,7 +334,7 @@ private void ReadFooter()
int crcval = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8) | ((footer[2] & 0xff) << 16) | (footer[3] << 24);
if (crcval != (int)crc.Value)
{
- throw new GZipException("GZIP crc sum mismatch, theirs \"" + crcval + "\" and ours \"" + (int)crc.Value);
+ throw new GZipException($"GZIP crc sum mismatch, theirs \"{crcval:x8}\" and ours \"{(int)crc.Value:x8}\"");
}
// NOTE The total here is the original total modulo 2 ^ 32.
diff --git a/src/ICSharpCode.SharpZipLib/GZip/GzipOutputStream.cs b/src/ICSharpCode.SharpZipLib/GZip/GzipOutputStream.cs
index 264f39a87..d4f1aa4c3 100644
--- a/src/ICSharpCode.SharpZipLib/GZip/GzipOutputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/GZip/GzipOutputStream.cs
@@ -138,6 +138,11 @@ public string FileName
}
}
+ ///
+ /// If defined, will use this time instead of the current for the output header
+ ///
+ public DateTime? ModifiedTime { get; set; }
+
#endregion Public API
#region Stream overrides
@@ -149,21 +154,47 @@ public string FileName
/// Offset of first byte in buf to write
/// Number of bytes to write
public override void Write(byte[] buffer, int offset, int count)
+ => WriteSyncOrAsync(buffer, offset, count, null).GetAwaiter().GetResult();
+
+ private async Task WriteSyncOrAsync(byte[] buffer, int offset, int count, CancellationToken? ct)
{
if (state_ == OutputState.Header)
{
- WriteHeader();
+ if (ct.HasValue)
+ {
+ await WriteHeaderAsync(ct.Value).ConfigureAwait(false);
+ }
+ else
+ {
+ WriteHeader();
+ }
}
if (state_ != OutputState.Footer)
- {
throw new InvalidOperationException("Write not permitted in current state");
- }
-
+
crc.Update(new ArraySegment(buffer, offset, count));
- base.Write(buffer, offset, count);
+
+ if (ct.HasValue)
+ {
+ await base.WriteAsync(buffer, offset, count, ct.Value).ConfigureAwait(false);
+ }
+ else
+ {
+ base.Write(buffer, offset, count);
+ }
}
+ ///
+ /// Asynchronously write given buffer to output updating crc
+ ///
+ /// Buffer to write
+ /// Offset of first byte in buf to write
+ /// Number of bytes to write
+ /// The token to monitor for cancellation requests
+ public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken ct)
+ => await WriteSyncOrAsync(buffer, offset, count, ct).ConfigureAwait(false);
+
///
/// Writes remaining compressed output data to the output stream
/// and closes it.
@@ -187,7 +218,7 @@ protected override void Dispose(bool disposing)
}
}
-#if NETSTANDARD2_1_OR_GREATER
+#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
///
public override async ValueTask DisposeAsync()
{
@@ -225,6 +256,16 @@ public override void Flush()
base.Flush();
}
+ ///
+ public override async Task FlushAsync(CancellationToken ct)
+ {
+ if (state_ == OutputState.Header)
+ {
+ await WriteHeaderAsync(ct).ConfigureAwait(false);
+ }
+ await base.FlushAsync(ct).ConfigureAwait(false);
+ }
+
#endregion Stream overrides
#region DeflaterOutputStream overrides
@@ -249,21 +290,13 @@ public override void Finish()
}
}
- ///
- public override async Task FlushAsync(CancellationToken ct)
- {
- await WriteHeaderAsync().ConfigureAwait(false);
- await base.FlushAsync(ct).ConfigureAwait(false);
- }
-
-
///
public override async Task FinishAsync(CancellationToken ct)
{
// If no data has been written a header should be added.
if (state_ == OutputState.Header)
{
- await WriteHeaderAsync().ConfigureAwait(false);
+ await WriteHeaderAsync(ct).ConfigureAwait(false);
}
if (state_ == OutputState.Footer)
@@ -305,7 +338,8 @@ private byte[] GetFooter()
private byte[] GetHeader()
{
- var modTime = (int)((DateTime.Now.Ticks - new DateTime(1970, 1, 1).Ticks) / 10000000L); // Ticks give back 100ns intervals
+ var modifiedUtc = ModifiedTime?.ToUniversalTime() ?? DateTime.UtcNow;
+ var modTime = (int)((modifiedUtc - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).Ticks / 10000000L); // Ticks give back 100ns intervals
byte[] gzipHeader = {
// The two magic bytes
GZipConstants.ID1,
@@ -351,12 +385,12 @@ private void WriteHeader()
baseOutputStream_.Write(gzipHeader, 0, gzipHeader.Length);
}
- private async Task WriteHeaderAsync()
+ private async Task WriteHeaderAsync(CancellationToken ct)
{
if (state_ != OutputState.Header) return;
state_ = OutputState.Footer;
var gzipHeader = GetHeader();
- await baseOutputStream_.WriteAsync(gzipHeader, 0, gzipHeader.Length).ConfigureAwait(false);
+ await baseOutputStream_.WriteAsync(gzipHeader, 0, gzipHeader.Length, ct).ConfigureAwait(false);
}
#endregion Support Routines
diff --git a/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs
index 48885e200..be63c7cfb 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs
@@ -240,11 +240,9 @@ protected void EncryptBlock(byte[] buffer, int offset, int length)
/// are processed.
///
protected void Deflate()
- {
- Deflate(false);
- }
+ => DeflateSyncOrAsync(false, null).GetAwaiter().GetResult();
- private void Deflate(bool flushing)
+ private async Task DeflateSyncOrAsync(bool flushing, CancellationToken? ct)
{
while (flushing || !deflater_.IsNeedingInput)
{
@@ -257,7 +255,14 @@ private void Deflate(bool flushing)
EncryptBlock(buffer_, 0, deflateCount);
- baseOutputStream_.Write(buffer_, 0, deflateCount);
+ if (ct.HasValue)
+ {
+ await baseOutputStream_.WriteAsync(buffer_, 0, deflateCount, ct.Value).ConfigureAwait(false);
+ }
+ else
+ {
+ baseOutputStream_.Write(buffer_, 0, deflateCount);
+ }
}
if (!deflater_.IsNeedingInput)
@@ -383,10 +388,18 @@ public override int Read(byte[] buffer, int offset, int count)
public override void Flush()
{
deflater_.Flush();
- Deflate(true);
+ DeflateSyncOrAsync(true, null).GetAwaiter().GetResult();
baseOutputStream_.Flush();
}
+ ///
+ public override async Task FlushAsync(CancellationToken cancellationToken)
+ {
+ deflater_.Flush();
+ await DeflateSyncOrAsync(true, cancellationToken).ConfigureAwait(false);
+ await baseOutputStream_.FlushAsync(cancellationToken).ConfigureAwait(false);
+ }
+
///
/// Calls and closes the underlying
/// stream when is true.
@@ -491,6 +504,13 @@ public override void Write(byte[] buffer, int offset, int count)
Deflate();
}
+ ///
+ public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken ct)
+ {
+ deflater_.SetInput(buffer, offset, count);
+ await DeflateSyncOrAsync(false, ct).ConfigureAwait(false);
+ }
+
#endregion Stream Overrides
#region Instance Fields
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs
index ecca5328a..2cc36df22 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs
@@ -552,7 +552,7 @@ await baseOutputStream_.WriteProcToStreamAsync(s =>
public void CloseEntry()
{
// Note: This method will run synchronously
- FinishCompression(null).Wait();
+ FinishCompressionSyncOrAsync(null).GetAwaiter().GetResult();
WriteEntryFooter(baseOutputStream_);
// Patch the header if possible
@@ -566,7 +566,7 @@ public void CloseEntry()
curEntry = null;
}
- private async Task FinishCompression(CancellationToken? ct)
+ private async Task FinishCompressionSyncOrAsync(CancellationToken? ct)
{
// Compression handled externally
if (entryIsPassthrough) return;
@@ -600,7 +600,7 @@ private async Task FinishCompression(CancellationToken? ct)
///
public async Task CloseEntryAsync(CancellationToken ct)
{
- await FinishCompression(ct).ConfigureAwait(false);
+ await FinishCompressionSyncOrAsync(ct).ConfigureAwait(false);
await baseOutputStream_.WriteProcToStreamAsync(WriteEntryFooter, ct).ConfigureAwait(false);
// Patch the header if possible
@@ -767,9 +767,7 @@ private byte[] CreateZipCryptoHeader(long crcValue)
private void InitializeZipCryptoPassword(string password)
{
var pkManaged = new PkzipClassicManaged();
- Console.WriteLine($"Output Encoding: {ZipCryptoEncoding.EncodingName}");
byte[] key = PkzipClassic.GenerateKeys(ZipCryptoEncoding.GetBytes(password));
- Console.WriteLine($"Output Bytes: {string.Join(", ", key.Select(b => $"{b:x2}").ToArray())}");
cryptoTransform_ = pkManaged.CreateEncryptor(key, null);
}
@@ -782,6 +780,13 @@ private void InitializeZipCryptoPassword(string password)
/// Archive size is invalid
/// No entry is active.
public override void Write(byte[] buffer, int offset, int count)
+ => WriteSyncOrAsync(buffer, offset, count, null).GetAwaiter().GetResult();
+
+ ///
+ public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken ct)
+ => await WriteSyncOrAsync(buffer, offset, count, ct).ConfigureAwait(false);
+
+ private async Task WriteSyncOrAsync(byte[] buffer, int offset, int count, CancellationToken? ct)
{
if (curEntry == null)
{
@@ -816,7 +821,7 @@ public override void Write(byte[] buffer, int offset, int count)
size += count;
- if(curMethod == CompressionMethod.Stored || entryIsPassthrough)
+ if (curMethod == CompressionMethod.Stored || entryIsPassthrough)
{
if (Password != null)
{
@@ -824,12 +829,26 @@ public override void Write(byte[] buffer, int offset, int count)
}
else
{
- baseOutputStream_.Write(buffer, offset, count);
+ if (ct.HasValue)
+ {
+ await baseOutputStream_.WriteAsync(buffer, offset, count, ct.Value).ConfigureAwait(false);
+ }
+ else
+ {
+ baseOutputStream_.Write(buffer, offset, count);
+ }
}
}
else
{
- base.Write(buffer, offset, count);
+ if (ct.HasValue)
+ {
+ await base.WriteAsync(buffer, offset, count, ct.Value).ConfigureAwait(false);
+ }
+ else
+ {
+ base.Write(buffer, offset, count);
+ }
}
}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/GZip/GZipAsyncTests.cs b/test/ICSharpCode.SharpZipLib.Tests/GZip/GZipAsyncTests.cs
index 209ae15d4..b259af8cf 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/GZip/GZipAsyncTests.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/GZip/GZipAsyncTests.cs
@@ -1,4 +1,5 @@
-using System.IO;
+using System;
+using System.IO;
using System.Text;
using System.Threading.Tasks;
using ICSharpCode.SharpZipLib.GZip;
@@ -7,8 +8,6 @@
namespace ICSharpCode.SharpZipLib.Tests.GZip
{
-
-
[TestFixture]
public class GZipAsyncTests
{
@@ -140,5 +139,48 @@ public async Task EmptyGZipStreamAsync()
Assert.IsEmpty(content);
}
}
+
+ [Test]
+ [Category("GZip")]
+ [Category("Async")]
+ public async Task WriteGZipStreamToAsyncOnlyStream()
+ {
+#if NETSTANDARD2_1 || NETCOREAPP3_0_OR_GREATER
+ var content = Encoding.ASCII.GetBytes("a");
+ var modTime = DateTime.UtcNow;
+
+ await using (var msAsync = new MemoryStreamWithoutSync())
+ {
+ await using (var outStream = new GZipOutputStream(msAsync) { IsStreamOwner = false })
+ {
+ outStream.ModifiedTime = modTime;
+ await outStream.WriteAsync(content);
+ }
+
+ using var msSync = new MemoryStream();
+ using (var outStream = new GZipOutputStream(msSync) { IsStreamOwner = false })
+ {
+ outStream.ModifiedTime = modTime;
+ outStream.Write(content);
+ }
+
+ var syncBytes = string.Join(' ', msSync.ToArray());
+ var asyncBytes = string.Join(' ', msAsync.ToArray());
+
+ Assert.AreEqual(syncBytes, asyncBytes, "Sync and Async compressed streams are not equal");
+
+ // Since GZipInputStream isn't async yet we need to read from it from a regular MemoryStream
+ using (var readStream = new MemoryStream(msAsync.ToArray()))
+ using (var inStream = new GZipInputStream(readStream))
+ using (var reader = new StreamReader(inStream))
+ {
+ Assert.AreEqual(content, await reader.ReadToEndAsync());
+ }
+ }
+#else
+ await Task.CompletedTask;
+ Assert.Ignore("AsyncDispose is not supported");
+#endif
+ }
}
}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipStreamAsyncTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipStreamAsyncTests.cs
index 6b55f8120..d228e5ee4 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipStreamAsyncTests.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipStreamAsyncTests.cs
@@ -124,17 +124,19 @@ public async Task WriteReadOnlyZipStreamAsync ()
[Test]
[Category("Zip")]
[Category("Async")]
- public async Task WriteZipStreamToAsyncOnlyStream ()
+ [TestCase(12, Description = "Small files")]
+ [TestCase(12000, Description = "Large files")]
+ public async Task WriteZipStreamToAsyncOnlyStream (int fileSize)
{
#if NETSTANDARD2_1 || NETCOREAPP3_0_OR_GREATER
await using(var ms = new MemoryStreamWithoutSync()){
await using(var outStream = new ZipOutputStream(ms) { IsStreamOwner = false })
{
await outStream.PutNextEntryAsync(new ZipEntry("FirstFile"));
- await Utils.WriteDummyDataAsync(outStream, 12);
+ await Utils.WriteDummyDataAsync(outStream, fileSize);
await outStream.PutNextEntryAsync(new ZipEntry("SecondFile"));
- await Utils.WriteDummyDataAsync(outStream, 12);
+ await Utils.WriteDummyDataAsync(outStream, fileSize);
await outStream.FinishAsync(CancellationToken.None);
await outStream.DisposeAsync();