diff --git a/Directory.Packages.props b/Directory.Packages.props
index 18d207e9..b9457b1d 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,7 +1,7 @@
-
+
@@ -9,16 +9,16 @@
-
+
-
+
diff --git a/build/packages.lock.json b/build/packages.lock.json
index cf4e18fe..6db93011 100644
--- a/build/packages.lock.json
+++ b/build/packages.lock.json
@@ -4,9 +4,9 @@
"net10.0": {
"Bullseye": {
"type": "Direct",
- "requested": "[6.2.0, )",
- "resolved": "6.2.0",
- "contentHash": "YWQxe+pvudGSzW1QqnRUlr9XV0xtlNvMxorVaFPqoMH8qAYNxniMQKGrPSiQkgEy0ckn/TAq98E+DIwTSR7vbQ=="
+ "requested": "[6.3.0, )",
+ "resolved": "6.3.0",
+ "contentHash": "yK14DF1aGvyGxIDFwApU1+YJcnXRmuVhGdz7js7eVTBnLvl/4rg20jb43YDhFvifcx7tRD4iOzF6ykoLdvWNfw=="
},
"Glob": {
"type": "Direct",
@@ -36,9 +36,9 @@
},
"Microsoft.VisualStudio.Threading.Analyzers": {
"type": "Direct",
- "requested": "[17.14.15, )",
- "resolved": "17.14.15",
- "contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
+ "requested": "[18.12.1, )",
+ "resolved": "18.12.1",
+ "contentHash": "sQt7NpnselT1gP1a7bTfhmfHYV0jbavst/8mOoWTMSjXhXzWPssqgBoIwrxNsgJUa7XQzD2raSRMAFfmlepxNw=="
},
"PolySharp": {
"type": "Direct",
@@ -54,9 +54,9 @@
},
"Zomp.SyncMethodGenerator": {
"type": "Direct",
- "requested": "[2.0.56, )",
- "resolved": "2.0.56",
- "contentHash": "P1iMA1meu0uX8YcPWhkFnpzV3e+QFDDYMU6hMNyofS+pBlLgeVV7QevBmJmJ/mrxA1jujo3cqNaO1IapdfjOlA=="
+ "requested": "[2.0.66, )",
+ "resolved": "2.0.66",
+ "contentHash": "iYnF185851PEX5vnxrhUj2av30HoxvjUlPd0HVVuSAif7TCgc/Noc5xVf9jiLhMlI8Rt9QrJdef3KB8+opAJQA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
diff --git a/docs/API.md b/docs/API.md
index 99cc48d2..295feeda 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -107,7 +107,7 @@ The stream overloads of `DetectArchive` and `InspectArchive` preserve the suppli
Stored (compression method 0) comments are supported up to 16 MiB of unpacked data; larger or unknown-size comments throw `InvalidFormatException`. Comments using other compression methods are skipped without affecting entry extraction.
-Truncated encrypted comment data throws `InvalidFormatException` on synchronous reads or `IncompleteArchiveException` on asynchronous reads. RAR5 KDF counts above 24 are rejected with `CryptographicException` before deriving encryption keys.
+Truncated encrypted comment data throws `InvalidFormatException` on both synchronous and asynchronous reads. RAR5 KDF counts above 24 are rejected with `CryptographicException` before deriving encryption keys.
```csharp
using var archive = RarArchive.OpenArchive(
@@ -502,6 +502,8 @@ archive.SaveTo("output.zip", options);
### Extraction behavior
+`ZipArchiveEntry.OpenEntryStreamAsync` and `TarArchiveEntry.OpenEntryStreamAsync` are virtual and support newly added writable entries as well as entries read from an archive. Opening a new writable entry checks cancellation, rewinds its supplied stream, and returns a non-owning wrapper. Disposing that wrapper leaves the supplied stream open; archive disposal follows the entry's `closeStream` setting.
+
```csharp
var options = new ExtractionOptions
{
@@ -692,6 +694,8 @@ using (var archive = ZipArchive.OpenArchive("archive.zip", options))
### Async Extract with Cancellation
+Async archive and reader/writer operations dispose SharpCompress resources asynchronously on every target, including .NET Framework 4.8 and .NET Standard 2.0. `SharpCompressStream.DisposeAsync()` and `ZlibStream.DisposeAsync()` are available on every target; stream wrappers forward disposal to runtime `IAsyncDisposable` implementations where available, falling back to synchronous disposal only for streams that do not support it.
+
```csharp
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromMinutes(5));
diff --git a/docs/FORMATS.md b/docs/FORMATS.md
index ab6c88f3..545706a9 100644
--- a/docs/FORMATS.md
+++ b/docs/FORMATS.md
@@ -45,7 +45,7 @@
- RAR5 encrypted headers and data accept KDF counts from 0 through 24 (the binary logarithm of the PBKDF2 iteration count), matching UnRAR's acceptance limit. Larger counts are rejected before key derivation.
- RAR companion filenames use the same exact-match-first, case-insensitive fallback as ZIP, with ambiguous fallback matches rejected.
-- Encrypted ciphertext must contain complete AES blocks. Truncation throws `InvalidFormatException` on synchronous reads or `IncompleteArchiveException` on asynchronous reads.
+- Encrypted ciphertext must contain complete AES blocks. Truncation throws `InvalidFormatException` on both synchronous and asynchronous reads.
### 7Zip Format Notes
diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.Async.cs b/src/SharpCompress/Archives/GZip/GZipArchive.Async.cs
index 7b2cda51..032e33f3 100644
--- a/src/SharpCompress/Archives/GZip/GZipArchive.Async.cs
+++ b/src/SharpCompress/Archives/GZip/GZipArchive.Async.cs
@@ -25,7 +25,12 @@ public partial class GZipArchive
CancellationToken cancellationToken = default
)
{
+#if SYNC_ONLY
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
+#else
+ var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
+ await using var scope = stream.DisposeAsyncScope().ConfigureAwait(false);
+#endif
await SaveToAsync(stream, new GZipWriterOptions(CompressionType.GZip), cancellationToken)
.ConfigureAwait(false);
}
@@ -50,9 +55,16 @@ public partial class GZipArchive
{
if (!entry.IsDirectory)
{
- using var entryStream = await entry
+#if SYNC_ONLY
+ using var entryStream = entry.OpenEntryStream();
+#else
+ var entryStream = await entry
.OpenEntryStreamAsync(cancellationToken)
.ConfigureAwait(false);
+ await using var entryStreamScope = entryStream
+ .DisposeAsyncScope()
+ .ConfigureAwait(false);
+#endif
await writer
.WriteAsync(
entry.Key.NotNull("Entry Key is null"),
@@ -65,9 +77,16 @@ public partial class GZipArchive
}
foreach (var entry in newEntries.Where(x => !x.IsDirectory))
{
- using var entryStream = await entry
+#if SYNC_ONLY
+ using var entryStream = entry.OpenEntryStream();
+#else
+ var entryStream = await entry
.OpenEntryStreamAsync(cancellationToken)
.ConfigureAwait(false);
+ await using var entryStreamScope = entryStream
+ .DisposeAsyncScope()
+ .ConfigureAwait(false);
+#endif
await writer
.WriteAsync(
entry.Key.NotNull("Entry Key is null"),
diff --git a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs
index 31b4596a..b4d858f2 100644
--- a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs
+++ b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs
@@ -164,7 +164,12 @@ public static partial class IArchiveEntryExtensions
options,
async (x, fm, ct) =>
{
+#if SYNC_ONLY
using var fs = File.Open(x, fm);
+#else
+ var fs = File.Open(x, fm);
+ await using var scope = fs.DisposeAsyncScope().ConfigureAwait(false);
+#endif
await entry
.WriteToAsync(fs, options.BufferSize, options, null, ct)
.ConfigureAwait(false);
diff --git a/src/SharpCompress/Archives/IWritableAsyncArchiveExtensions.cs b/src/SharpCompress/Archives/IWritableAsyncArchiveExtensions.cs
index dcfee164..d74b756b 100644
--- a/src/SharpCompress/Archives/IWritableAsyncArchiveExtensions.cs
+++ b/src/SharpCompress/Archives/IWritableAsyncArchiveExtensions.cs
@@ -83,7 +83,8 @@ public static class IWritableAsyncArchiveExtensions
)
where TOptions : IWriterOptions
{
- using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
+ var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
+ await using var scope = stream.DisposeAsyncScope().ConfigureAwait(false);
await writableArchive.SaveToAsync(stream, options, cancellationToken).ConfigureAwait(false);
}
}
diff --git a/src/SharpCompress/Archives/Tar/TarArchive.Async.cs b/src/SharpCompress/Archives/Tar/TarArchive.Async.cs
index d8991d43..2c5e69e0 100644
--- a/src/SharpCompress/Archives/Tar/TarArchive.Async.cs
+++ b/src/SharpCompress/Archives/Tar/TarArchive.Async.cs
@@ -22,7 +22,7 @@ public partial class TarArchive
CancellationToken cancellationToken = default
)
{
- using var writer = new TarWriter(stream, options);
+ await using var writer = new TarWriter(stream, options);
await foreach (
var entry in oldEntries.WithCancellation(cancellationToken).ConfigureAwait(false)
)
@@ -39,7 +39,12 @@ public partial class TarArchive
}
else
{
- using var entryStream = entry.OpenEntryStream();
+ var entryStream = await entry
+ .OpenEntryStreamAsync(cancellationToken)
+ .ConfigureAwait(false);
+ await using var entryStreamScope = entryStream
+ .DisposeAsyncScope()
+ .ConfigureAwait(false);
await writer
.WriteAsync(
entry.Key.NotNull("Entry Key is null"),
@@ -65,7 +70,12 @@ public partial class TarArchive
}
else
{
- using var entryStream = entry.OpenEntryStream();
+ var entryStream = await entry
+ .OpenEntryStreamAsync(cancellationToken)
+ .ConfigureAwait(false);
+ await using var entryStreamScope = entryStream
+ .DisposeAsyncScope()
+ .ConfigureAwait(false);
await writer
.WriteAsync(
entry.Key.NotNull("Entry Key is null"),
diff --git a/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs b/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs
index c09d4755..88f832f6 100644
--- a/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs
+++ b/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs
@@ -20,7 +20,7 @@ public class TarArchiveEntry : TarEntry, IArchiveEntry
public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream().NotNull();
- public async ValueTask OpenEntryStreamAsync(
+ public virtual async ValueTask OpenEntryStreamAsync(
CancellationToken cancellationToken = default
) =>
(
diff --git a/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs b/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs
index 84f8ff44..c1a81f93 100644
--- a/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs
+++ b/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.IO;
@@ -82,6 +84,15 @@ internal sealed class TarWritableArchiveEntry : TarArchiveEntry, IWritableArchiv
return SharpCompressStream.CreateNonDisposing(stream);
}
+ public override ValueTask OpenEntryStreamAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ // Writable entries have no archive parts; opening only rewinds the supplied stream.
+ return new(OpenEntryStream());
+ }
+
internal override void Close()
{
if (closeStream && stream is not null)
diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.Async.cs b/src/SharpCompress/Archives/Zip/ZipArchive.Async.cs
index 97c412e6..122112eb 100644
--- a/src/SharpCompress/Archives/Zip/ZipArchive.Async.cs
+++ b/src/SharpCompress/Archives/Zip/ZipArchive.Async.cs
@@ -84,7 +84,7 @@ public partial class ZipArchive
CancellationToken cancellationToken = default
)
{
- using var writer = new ZipWriter(stream, options);
+ await using var writer = new ZipWriter(stream, options);
await foreach (
var entry in oldEntries.WithCancellation(cancellationToken).ConfigureAwait(false)
)
@@ -101,7 +101,12 @@ public partial class ZipArchive
}
else
{
- using var entryStream = entry.OpenEntryStream();
+ var entryStream = await entry
+ .OpenEntryStreamAsync(cancellationToken)
+ .ConfigureAwait(false);
+ await using var entryStreamScope = entryStream
+ .DisposeAsyncScope()
+ .ConfigureAwait(false);
await writer
.WriteAsync(
entry.Key.NotNull("Entry Key is null"),
@@ -125,7 +130,12 @@ public partial class ZipArchive
}
else
{
- using var entryStream = entry.OpenEntryStream();
+ var entryStream = await entry
+ .OpenEntryStreamAsync(cancellationToken)
+ .ConfigureAwait(false);
+ await using var entryStreamScope = entryStream
+ .DisposeAsyncScope()
+ .ConfigureAwait(false);
await writer
.WriteAsync(
entry.Key.NotNull("Entry Key is null"),
diff --git a/src/SharpCompress/Archives/Zip/ZipArchiveEntry.Async.cs b/src/SharpCompress/Archives/Zip/ZipArchiveEntry.Async.cs
index 308727a6..063b457c 100644
--- a/src/SharpCompress/Archives/Zip/ZipArchiveEntry.Async.cs
+++ b/src/SharpCompress/Archives/Zip/ZipArchiveEntry.Async.cs
@@ -8,7 +8,7 @@ namespace SharpCompress.Archives.Zip;
public partial class ZipArchiveEntry
{
- public async ValueTask OpenEntryStreamAsync(
+ public virtual async ValueTask OpenEntryStreamAsync(
CancellationToken cancellationToken = default
)
{
diff --git a/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs b/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs
index b74fea04..d6995e55 100644
--- a/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs
+++ b/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.IO;
@@ -83,6 +85,15 @@ internal class ZipWritableArchiveEntry : ZipArchiveEntry, IWritableArchiveEntry
return SharpCompressStream.CreateNonDisposing(stream);
}
+ public override ValueTask OpenEntryStreamAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ // Writable entries have no archive parts; opening only rewinds the supplied stream.
+ return new(OpenEntryStream());
+ }
+
internal override void Close()
{
if (closeStream && !isDisposed && stream is not null)
diff --git a/src/SharpCompress/Common/Rar/Headers/MarkHeader.Async.cs b/src/SharpCompress/Common/Rar/Headers/MarkHeader.Async.cs
index 77cf4476..52b0ad4f 100644
--- a/src/SharpCompress/Common/Rar/Headers/MarkHeader.Async.cs
+++ b/src/SharpCompress/Common/Rar/Headers/MarkHeader.Async.cs
@@ -122,11 +122,7 @@ internal partial class MarkHeader
{
if (!leaveStreamOpen)
{
-#if LEGACY_DOTNET && !NETSTANDARD2_1
- stream.Dispose();
-#else
- await stream.DisposeAsync().ConfigureAwait(false);
-#endif
+ await stream.DisposeAsyncCompat().ConfigureAwait(false);
}
throw new InvalidFormatException("Error trying to read rar signature.", e);
}
diff --git a/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs b/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs
index c4dc0b72..1d0608a2 100644
--- a/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs
+++ b/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs
@@ -1,10 +1,11 @@
using System;
using System.IO;
using System.Threading.Tasks;
+using SharpCompress.IO;
namespace SharpCompress.Common.Tar;
-internal class TarReadOnlySubStream : Stream
+internal class TarReadOnlySubStream : AsyncDisposableStream
{
private readonly Stream _stream;
@@ -34,7 +35,6 @@ internal class TarReadOnlySubStream : Stream
base.Dispose(disposing);
}
-#if !LEGACY_DOTNET
public override async System.Threading.Tasks.ValueTask DisposeAsync()
{
if (_isDisposed)
@@ -49,7 +49,6 @@ internal class TarReadOnlySubStream : Stream
GC.SuppressFinalize(this);
await base.DisposeAsync().ConfigureAwait(false);
}
-#endif
private long BytesLeftToRead { get; set; }
diff --git a/src/SharpCompress/Common/Volume.Async.cs b/src/SharpCompress/Common/Volume.Async.cs
index 3a07aece..05984162 100644
--- a/src/SharpCompress/Common/Volume.Async.cs
+++ b/src/SharpCompress/Common/Volume.Async.cs
@@ -7,12 +7,7 @@ public abstract partial class Volume
{
public virtual async ValueTask DisposeAsync()
{
-#if LEGACY_DOTNET
- _actualStream.Dispose();
- await Task.CompletedTask.ConfigureAwait(false);
-#else
- await _actualStream.DisposeAsync().ConfigureAwait(false);
-#endif
+ await _actualStream.DisposeAsyncCompat().ConfigureAwait(false);
GC.SuppressFinalize(this);
}
}
diff --git a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs
index 2ee0b2ea..242b8515 100644
--- a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs
+++ b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs
@@ -10,7 +10,6 @@ namespace SharpCompress.Common.Zip;
internal partial class WinzipAesCryptoStream
{
-#if !LEGACY_DOTNET
public override async ValueTask DisposeAsync()
{
if (_isDisposed)
@@ -31,11 +30,10 @@ internal partial class WinzipAesCryptoStream
_authentication.Dispose();
_transform.Dispose();
_cipher.Dispose();
- await _stream.DisposeAsync().ConfigureAwait(false);
+ await _stream.DisposeAsyncCompat().ConfigureAwait(false);
}
await base.DisposeAsync().ConfigureAwait(false);
}
-#endif
private async ValueTask VerifyAuthenticationAsync(CancellationToken cancellationToken)
{
@@ -44,17 +42,37 @@ internal partial class WinzipAesCryptoStream
{
return;
}
- _authRead = true;
- var tag = new byte[10];
try
{
- await _stream
- .ReadExactAsync(tag, 0, tag.Length, cancellationToken)
- .ConfigureAwait(false);
- ValidateAuthentication(tag);
+ while (_authenticationTagBytesRead < _authenticationTag.Length)
+ {
+ var read = await _stream
+ .ReadAsync(
+ _authenticationTag,
+ _authenticationTagBytesRead,
+ _authenticationTag.Length - _authenticationTagBytesRead,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
+ if (read == 0)
+ {
+ throw new IncompleteArchiveException(
+ "Unexpected end of WinZip AES authentication tag."
+ );
+ }
+ _authenticationTagBytesRead += read;
+ }
+ ValidateAuthentication(_authenticationTag);
+ _authRead = true;
+ }
+ catch (OperationCanceledException)
+ {
+ // Cancellation is transient; keep the tag prefix so disposal can finish verification.
+ throw;
}
catch (Exception exception)
{
+ _authRead = true;
_authenticationFailure = ExceptionDispatchInfo.Capture(exception);
throw;
}
diff --git a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs
index 1ec17808..b96ca1eb 100644
--- a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs
+++ b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs
@@ -3,10 +3,11 @@ using System.Buffers.Binary;
using System.IO;
using System.Runtime.ExceptionServices;
using System.Security.Cryptography;
+using SharpCompress.IO;
namespace SharpCompress.Common.Zip;
-internal partial class WinzipAesCryptoStream : Stream
+internal partial class WinzipAesCryptoStream : AsyncDisposableStream
{
private const int BLOCK_SIZE_IN_BYTES = 16;
private readonly Aes _cipher;
@@ -15,6 +16,10 @@ internal partial class WinzipAesCryptoStream : Stream
private readonly ICryptoTransform _transform;
private readonly HMACSHA1 _authentication;
private bool _authRead;
+
+ // Retain tag progress across canceled reads, including a subsequent synchronous disposal.
+ private readonly byte[] _authenticationTag = new byte[10];
+ private int _authenticationTagBytesRead;
private ExceptionDispatchInfo? _authenticationFailure;
private int _nonce = 1;
private byte[] _counterOut = new byte[BLOCK_SIZE_IN_BYTES];
@@ -83,7 +88,7 @@ internal partial class WinzipAesCryptoStream : Stream
{
if (!_authRead)
{
-#if LEGACY_DOTNET
+#if !STREAM_DISPOSEASYNC
// Legacy Stream lacks DisposeAsync, including for async-only inputs.
#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits
#pragma warning disable CA2012
@@ -118,15 +123,33 @@ internal partial class WinzipAesCryptoStream : Stream
{
return;
}
- _authRead = true;
- var tag = new byte[10];
try
{
- _stream.ReadExact(tag, 0, tag.Length);
- ValidateAuthentication(tag);
+ while (_authenticationTagBytesRead < _authenticationTag.Length)
+ {
+ var read = _stream.Read(
+ _authenticationTag,
+ _authenticationTagBytesRead,
+ _authenticationTag.Length - _authenticationTagBytesRead
+ );
+ if (read == 0)
+ {
+ throw new IncompleteArchiveException(
+ "Unexpected end of WinZip AES authentication tag."
+ );
+ }
+ _authenticationTagBytesRead += read;
+ }
+ ValidateAuthentication(_authenticationTag);
+ _authRead = true;
+ }
+ catch (OperationCanceledException)
+ {
+ throw;
}
catch (Exception exception)
{
+ _authRead = true;
_authenticationFailure = ExceptionDispatchInfo.Capture(exception);
throw;
}
diff --git a/src/SharpCompress/Compressors/BZip2/BZip2Stream.Async.cs b/src/SharpCompress/Compressors/BZip2/BZip2Stream.Async.cs
index 35cf9f1d..c7b33645 100644
--- a/src/SharpCompress/Compressors/BZip2/BZip2Stream.Async.cs
+++ b/src/SharpCompress/Compressors/BZip2/BZip2Stream.Async.cs
@@ -115,7 +115,7 @@ public sealed partial class BZip2Stream : IAsyncDisposable
CancellationToken cancellationToken = default
) => await stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
-#if !LEGACY_DOTNET || NETSTANDARD2_1
+#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -136,7 +136,7 @@ public sealed partial class BZip2Stream : IAsyncDisposable
stream.Dispose();
}
-#if !LEGACY_DOTNET || NETSTANDARD2_1
+#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
#else
await Task.CompletedTask.ConfigureAwait(false);
diff --git a/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.Async.cs b/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.Async.cs
index c1053216..ab85c1ef 100644
--- a/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.Async.cs
+++ b/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.Async.cs
@@ -707,7 +707,7 @@ internal partial class CBZip2InputStream
}
else
{
- SetupNoRandPartA();
+ await SetupNoRandPartAAsync(cancellationToken).ConfigureAwait(false);
}
}
@@ -792,7 +792,7 @@ internal partial class CBZip2InputStream
z ^= (char)((rNToGo == 1) ? 1 : 0);
j2 = 0;
currentState = RAND_PART_C_STATE;
- SetupRandPartC();
+ await SetupRandPartCAsync(cancellationToken).ConfigureAwait(false);
}
else
{
diff --git a/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.Async.cs b/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.Async.cs
index d56e9244..6db68c8f 100644
--- a/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.Async.cs
+++ b/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.Async.cs
@@ -670,7 +670,7 @@ internal sealed partial class CBZip2OutputStream : IAsyncDisposable
await bsStream.FlushAsync(cancellationToken).ConfigureAwait(false);
}
-#if !LEGACY_DOTNET || NETSTANDARD2_1
+#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -696,7 +696,7 @@ internal sealed partial class CBZip2OutputStream : IAsyncDisposable
}
bsStream = null;
-#if !LEGACY_DOTNET || NETSTANDARD2_1
+#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
#else
await Task.CompletedTask.ConfigureAwait(false);
diff --git a/src/SharpCompress/Compressors/Deflate/DeflateStream.Async.cs b/src/SharpCompress/Compressors/Deflate/DeflateStream.Async.cs
index ae6343cd..496adca2 100644
--- a/src/SharpCompress/Compressors/Deflate/DeflateStream.Async.cs
+++ b/src/SharpCompress/Compressors/Deflate/DeflateStream.Async.cs
@@ -8,7 +8,7 @@ namespace SharpCompress.Compressors.Deflate;
public partial class DeflateStream
{
-#if !LEGACY_DOTNET || NETSTANDARD2_1
+#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -22,7 +22,7 @@ public partial class DeflateStream
}
_disposed = true;
}
-#if !LEGACY_DOTNET || NETSTANDARD2_1
+#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
#else
await Task.CompletedTask.ConfigureAwait(false);
diff --git a/src/SharpCompress/Compressors/Deflate/GZipStream.Async.cs b/src/SharpCompress/Compressors/Deflate/GZipStream.Async.cs
index 4a70c519..db8bfbcc 100644
--- a/src/SharpCompress/Compressors/Deflate/GZipStream.Async.cs
+++ b/src/SharpCompress/Compressors/Deflate/GZipStream.Async.cs
@@ -183,7 +183,7 @@ public partial class GZipStream
}
#endif
-#if !LEGACY_DOTNET || NETSTANDARD2_1
+#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -200,7 +200,7 @@ public partial class GZipStream
await BaseStream.DisposeAsync().ConfigureAwait(false);
}
-#if !LEGACY_DOTNET || NETSTANDARD2_1
+#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
#endif
}
diff --git a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs
index adb462b1..b574e74c 100644
--- a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs
+++ b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs
@@ -510,7 +510,7 @@ internal partial class ZlibBaseStream : Stream, IStreamStack
}
}
-#if !LEGACY_DOTNET || NETSTANDARD2_1
+#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -521,7 +521,7 @@ internal partial class ZlibBaseStream : Stream, IStreamStack
return;
}
isDisposed = true;
-#if !LEGACY_DOTNET || NETSTANDARD2_1
+#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
#endif
if (_stream is null)
diff --git a/src/SharpCompress/Compressors/Deflate/ZlibStream.Async.cs b/src/SharpCompress/Compressors/Deflate/ZlibStream.Async.cs
index 058bd28e..1021bbde 100644
--- a/src/SharpCompress/Compressors/Deflate/ZlibStream.Async.cs
+++ b/src/SharpCompress/Compressors/Deflate/ZlibStream.Async.cs
@@ -21,8 +21,11 @@ public partial class ZlibStream
await _baseStream.FlushAsync(cancellationToken).ConfigureAwait(false);
}
-#if !LEGACY_DOTNET
+#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
+#else
+ public virtual async ValueTask DisposeAsync()
+#endif
{
if (_disposed)
{
@@ -33,9 +36,13 @@ public partial class ZlibStream
{
await _baseStream.DisposeAsync().ConfigureAwait(false);
}
+#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
- }
+#else
+ base.Dispose();
+ GC.SuppressFinalize(this);
#endif
+ }
///
/// Read data from the stream.
diff --git a/src/SharpCompress/Compressors/Deflate/ZlibStream.cs b/src/SharpCompress/Compressors/Deflate/ZlibStream.cs
index 580961d7..97c77538 100644
--- a/src/SharpCompress/Compressors/Deflate/ZlibStream.cs
+++ b/src/SharpCompress/Compressors/Deflate/ZlibStream.cs
@@ -33,6 +33,9 @@ using SharpCompress.Common;
namespace SharpCompress.Compressors.Deflate;
public partial class ZlibStream : Stream
+#if !STREAM_DISPOSEASYNC
+ , IAsyncDisposable
+#endif
{
private readonly ZlibBaseStream _baseStream;
private bool _disposed;
diff --git a/src/SharpCompress/Compressors/LZMA/Lzma2EncoderStream.cs b/src/SharpCompress/Compressors/LZMA/Lzma2EncoderStream.cs
index 7440ff66..2790b7c5 100644
--- a/src/SharpCompress/Compressors/LZMA/Lzma2EncoderStream.cs
+++ b/src/SharpCompress/Compressors/LZMA/Lzma2EncoderStream.cs
@@ -200,7 +200,7 @@ internal sealed class Lzma2EncoderStream : Stream
private static readonly byte[] _endMarker = [0x00];
-#if !LEGACY_DOTNET || NETSTANDARD2_1
+#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -229,7 +229,7 @@ internal sealed class Lzma2EncoderStream : Stream
}
}
-#if !LEGACY_DOTNET || NETSTANDARD2_1
+#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
#endif
}
diff --git a/src/SharpCompress/Compressors/LZMA/LzmaStream.Async.cs b/src/SharpCompress/Compressors/LZMA/LzmaStream.Async.cs
index 1c87d5e1..0c23a259 100644
--- a/src/SharpCompress/Compressors/LZMA/LzmaStream.Async.cs
+++ b/src/SharpCompress/Compressors/LZMA/LzmaStream.Async.cs
@@ -454,7 +454,7 @@ public partial class LzmaStream
}
#endif
-#if !LEGACY_DOTNET || NETSTANDARD2_1
+#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -494,7 +494,7 @@ public partial class LzmaStream
}
}
-#if !LEGACY_DOTNET || NETSTANDARD2_1
+#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
#endif
GC.SuppressFinalize(this);
diff --git a/src/SharpCompress/Compressors/PPMd/PpmdStream.cs b/src/SharpCompress/Compressors/PPMd/PpmdStream.cs
index 744682c4..bcf39c90 100644
--- a/src/SharpCompress/Compressors/PPMd/PpmdStream.cs
+++ b/src/SharpCompress/Compressors/PPMd/PpmdStream.cs
@@ -160,11 +160,7 @@ public class PpmdStream : Stream, IAsyncDisposable
}
catch
{
-#if LEGACY_DOTNET && !NETSTANDARD2_1
- instance.Dispose();
-#else
await instance.DisposeAsync().ConfigureAwait(false);
-#endif
throw;
}
}
@@ -196,7 +192,7 @@ public class PpmdStream : Stream, IAsyncDisposable
base.Dispose(disposing);
}
-#if !LEGACY_DOTNET || NETSTANDARD2_1
+#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -215,7 +211,7 @@ public class PpmdStream : Stream, IAsyncDisposable
_modelH?.Dispose();
_modelH = null;
-#if !LEGACY_DOTNET || NETSTANDARD2_1
+#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
#endif
}
diff --git a/src/SharpCompress/Compressors/Xz/XZFooter.Async.cs b/src/SharpCompress/Compressors/Xz/XZFooter.Async.cs
index b136335d..de7b8fe9 100644
--- a/src/SharpCompress/Compressors/Xz/XZFooter.Async.cs
+++ b/src/SharpCompress/Compressors/Xz/XZFooter.Async.cs
@@ -36,7 +36,9 @@ public partial class XZFooter
using (var reader = new BinaryReader(stream))
{
BackwardSize = (reader.ReadLittleEndianUInt32() + 1) * 4;
- StreamFlags = reader.ReadBytes(2);
+ StreamFlags = await reader
+ .ReadBytesAsync(2, cancellationToken: cancellationToken)
+ .ConfigureAwait(false);
}
var magBy = await _reader.ReadBytesAsync(2, cancellationToken).ConfigureAwait(false);
if (!magBy.AsSpan().SequenceEqual(_magicBytes))
diff --git a/src/SharpCompress/Compressors/ZStandard/CompressionStream.Async.cs b/src/SharpCompress/Compressors/ZStandard/CompressionStream.Async.cs
index 5f988e4c..d788d152 100644
--- a/src/SharpCompress/Compressors/ZStandard/CompressionStream.Async.cs
+++ b/src/SharpCompress/Compressors/ZStandard/CompressionStream.Async.cs
@@ -8,7 +8,7 @@ namespace SharpCompress.Compressors.ZStandard;
public partial class CompressionStream
{
-#if !LEGACY_DOTNET || NETSTANDARD2_1
+#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -16,7 +16,7 @@ public partial class CompressionStream
{
if (compressor == null)
{
-#if LEGACY_DOTNET
+#if !STREAM_DISPOSEASYNC
Dispose(true);
GC.SuppressFinalize(this);
await Task.CompletedTask.ConfigureAwait(false);
@@ -35,7 +35,7 @@ public partial class CompressionStream
ReleaseUnmanagedResources();
GC.SuppressFinalize(this);
}
-#if LEGACY_DOTNET
+#if !STREAM_DISPOSEASYNC
Dispose(true);
await Task.CompletedTask.ConfigureAwait(false);
#else
diff --git a/src/SharpCompress/Compressors/ZStandard/DecompressionStream.cs b/src/SharpCompress/Compressors/ZStandard/DecompressionStream.cs
index 20490d91..c4161fd2 100644
--- a/src/SharpCompress/Compressors/ZStandard/DecompressionStream.cs
+++ b/src/SharpCompress/Compressors/ZStandard/DecompressionStream.cs
@@ -203,7 +203,7 @@ public partial class DecompressionStream : Stream
}
}
-#if LEGACY_DOTNET && !NETSTANDARD2_1
+#if !STREAM_DISPOSEASYNC
public virtual ValueTask DisposeAsync()
{
try
diff --git a/src/SharpCompress/IO/AsyncDisposableStream.cs b/src/SharpCompress/IO/AsyncDisposableStream.cs
index 4c7fafbe..e528b4ce 100644
--- a/src/SharpCompress/IO/AsyncDisposableStream.cs
+++ b/src/SharpCompress/IO/AsyncDisposableStream.cs
@@ -22,11 +22,11 @@ namespace SharpCompress.IO;
///
///
public abstract class AsyncDisposableStream : Stream
-#if NO_STREAM_DISPOSEASYNC
+#if !STREAM_DISPOSEASYNC
, IAsyncDisposable
#endif
{
-#if NO_STREAM_DISPOSEASYNC
+#if !STREAM_DISPOSEASYNC
public virtual ValueTask DisposeAsync()
{
Dispose();
diff --git a/src/SharpCompress/IO/ProgressReportingStream.Async.cs b/src/SharpCompress/IO/ProgressReportingStream.Async.cs
index fd10398b..096c0a95 100644
--- a/src/SharpCompress/IO/ProgressReportingStream.Async.cs
+++ b/src/SharpCompress/IO/ProgressReportingStream.Async.cs
@@ -45,14 +45,12 @@ internal sealed partial class ProgressReportingStream
}
#endif
-#if !LEGACY_DOTNET
public override async ValueTask DisposeAsync()
{
if (!_leaveOpen)
{
- await _baseStream.DisposeAsync().ConfigureAwait(false);
+ await _baseStream.DisposeAsyncCompat().ConfigureAwait(false);
}
await base.DisposeAsync().ConfigureAwait(false);
}
-#endif
}
diff --git a/src/SharpCompress/IO/ProgressReportingStream.cs b/src/SharpCompress/IO/ProgressReportingStream.cs
index 8834a546..4eb73bab 100644
--- a/src/SharpCompress/IO/ProgressReportingStream.cs
+++ b/src/SharpCompress/IO/ProgressReportingStream.cs
@@ -8,7 +8,7 @@ namespace SharpCompress.IO;
/// A stream wrapper that reports progress as data is read from the source.
/// Used to track compression or extraction progress by wrapping the source stream.
///
-internal sealed partial class ProgressReportingStream : Stream
+internal sealed partial class ProgressReportingStream : AsyncDisposableStream
{
private readonly Stream _baseStream;
private readonly IProgress _progress;
diff --git a/src/SharpCompress/IO/SeekableSharpCompressStream.Async.cs b/src/SharpCompress/IO/SeekableSharpCompressStream.Async.cs
index 1bac16de..7bf2b650 100644
--- a/src/SharpCompress/IO/SeekableSharpCompressStream.Async.cs
+++ b/src/SharpCompress/IO/SeekableSharpCompressStream.Async.cs
@@ -28,12 +28,14 @@ internal sealed partial class SeekableSharpCompressStream
ReadOnlyMemory buffer,
CancellationToken cancellationToken = default
) => _stream.WriteAsync(buffer, cancellationToken);
+#endif
- public override ValueTask DisposeAsync()
+ public override async ValueTask DisposeAsync()
{
if (_isDisposed)
{
- return base.DisposeAsync();
+ await base.DisposeAsync().ConfigureAwait(false);
+ return;
}
if (ThrowOnDispose)
{
@@ -44,11 +46,10 @@ internal sealed partial class SeekableSharpCompressStream
_isDisposed = true;
if (!LeaveStreamOpen)
{
- _stream.Dispose();
+ await _stream.DisposeAsyncCompat().ConfigureAwait(false);
}
- return base.DisposeAsync();
+ await base.DisposeAsync().ConfigureAwait(false);
}
-#endif
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override Task WriteAsync(
diff --git a/src/SharpCompress/IO/SharpCompressStream.Async.cs b/src/SharpCompress/IO/SharpCompressStream.Async.cs
index 7b1f376d..47ba651e 100644
--- a/src/SharpCompress/IO/SharpCompressStream.Async.cs
+++ b/src/SharpCompress/IO/SharpCompressStream.Async.cs
@@ -251,8 +251,11 @@ public partial class SharpCompressStream
}
}
-#if !LEGACY_DOTNET
+#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
+#else
+ public virtual async ValueTask DisposeAsync()
+#endif
{
if (!isDisposed)
{
@@ -267,7 +270,7 @@ public partial class SharpCompressStream
{
if (!LeaveStreamOpen)
{
- await stream.DisposeAsync().ConfigureAwait(false);
+ await stream.DisposeAsyncCompat().ConfigureAwait(false);
}
}
finally
@@ -276,7 +279,11 @@ public partial class SharpCompressStream
_ringBuffer = null;
}
}
+#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
- }
+#else
+ base.Dispose();
+ GC.SuppressFinalize(this);
#endif
+ }
}
diff --git a/src/SharpCompress/IO/SharpCompressStream.cs b/src/SharpCompress/IO/SharpCompressStream.cs
index 7a5b7777..d59c53c2 100644
--- a/src/SharpCompress/IO/SharpCompressStream.cs
+++ b/src/SharpCompress/IO/SharpCompressStream.cs
@@ -17,6 +17,9 @@ namespace SharpCompress.IO;
/// stream and usage scenario.
///
public partial class SharpCompressStream : Stream, IStreamStack
+#if !STREAM_DISPOSEASYNC
+ , IAsyncDisposable
+#endif
{
public virtual Stream BaseStream() => stream;
diff --git a/src/SharpCompress/Readers/IAsyncReaderExtensions.cs b/src/SharpCompress/Readers/IAsyncReaderExtensions.cs
index 5b4e667b..c962d116 100644
--- a/src/SharpCompress/Readers/IAsyncReaderExtensions.cs
+++ b/src/SharpCompress/Readers/IAsyncReaderExtensions.cs
@@ -45,7 +45,8 @@ public static class IAsyncReaderExtensions
options,
async (x, fm, ct) =>
{
- using var fs = File.Open(x, fm);
+ var fs = File.Open(x, fm);
+ await using var scope = fs.DisposeAsyncScope().ConfigureAwait(false);
await CopyEntryToAsync(reader, fs, options, ct).ConfigureAwait(false);
},
cancellationToken
@@ -83,7 +84,8 @@ public static class IAsyncReaderExtensions
options,
async (x, fm, ct) =>
{
- using var fs = File.Open(x, fm);
+ var fs = File.Open(x, fm);
+ await using var scope = fs.DisposeAsyncScope().ConfigureAwait(false);
await CopyEntryToAsync(reader, fs, options, ct).ConfigureAwait(false);
},
cancellationToken
diff --git a/src/SharpCompress/SharpCompress.csproj b/src/SharpCompress/SharpCompress.csproj
index c57cb722..d5ba7f80 100644
--- a/src/SharpCompress/SharpCompress.csproj
+++ b/src/SharpCompress/SharpCompress.csproj
@@ -31,8 +31,8 @@
$(DefineConstants);LEGACY_DOTNET
-
- $(DefineConstants);NO_STREAM_DISPOSEASYNC
+
+ $(DefineConstants);STREAM_DISPOSEASYNC
true
diff --git a/src/SharpCompress/Utility.Async.cs b/src/SharpCompress/Utility.Async.cs
index df323d69..6e6debff 100644
--- a/src/SharpCompress/Utility.Async.cs
+++ b/src/SharpCompress/Utility.Async.cs
@@ -70,7 +70,8 @@ internal static partial class Utility
)
{
// Use ReadOnlySubStream to limit reading and leverage framework's CopyToAsync
- using var limitedStream = new IO.ReadOnlySubStream(source, maxLength);
+ var limitedStream = new IO.ReadOnlySubStream(source, maxLength);
+ await using var scope = limitedStream.DisposeAsyncScope().ConfigureAwait(false);
await limitedStream
.CopyToAsync(destination, bufferSize ?? Constants.BufferSize, cancellationToken)
.ConfigureAwait(false);
diff --git a/src/SharpCompress/Writers/IWriterExtensions.cs b/src/SharpCompress/Writers/IWriterExtensions.cs
index a178dd5d..b1a082f9 100644
--- a/src/SharpCompress/Writers/IWriterExtensions.cs
+++ b/src/SharpCompress/Writers/IWriterExtensions.cs
@@ -77,7 +77,8 @@ public static class IWriterExtensions
{
throw new ArgumentException("Source does not exist: " + source.FullName);
}
- using var stream = source.OpenRead();
+ var stream = source.OpenRead();
+ await using var scope = stream.DisposeAsyncScope().ConfigureAwait(false);
await writer
.WriteAsync(entryPath, stream, source.LastWriteTime, cancellationToken)
.ConfigureAwait(false);
diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json
index f4c3588e..6afa698b 100644
--- a/src/SharpCompress/packages.lock.json
+++ b/src/SharpCompress/packages.lock.json
@@ -33,9 +33,9 @@
},
"Microsoft.VisualStudio.Threading.Analyzers": {
"type": "Direct",
- "requested": "[17.14.15, )",
- "resolved": "17.14.15",
- "contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
+ "requested": "[18.12.1, )",
+ "resolved": "18.12.1",
+ "contentHash": "sQt7NpnselT1gP1a7bTfhmfHYV0jbavst/8mOoWTMSjXhXzWPssqgBoIwrxNsgJUa7XQzD2raSRMAFfmlepxNw=="
},
"PolySharp": {
"type": "Direct",
@@ -45,19 +45,20 @@
},
"System.Text.Encoding.CodePages": {
"type": "Direct",
- "requested": "[8.0.0, )",
- "resolved": "8.0.0",
- "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==",
+ "requested": "[10.0.12, )",
+ "resolved": "10.0.12",
+ "contentHash": "rt1G/LdKC6oc3xPyQhMn5vRTp9iTTt1BS9Dx/67TpW9KWgFJ39lyEz0y4CbDksx7iTZEfgG61UsQMoGEXEpF4A==",
"dependencies": {
- "System.Memory": "4.5.5",
- "System.Runtime.CompilerServices.Unsafe": "6.0.0"
+ "System.Memory": "4.6.3",
+ "System.Runtime.CompilerServices.Unsafe": "6.1.2",
+ "System.ValueTuple": "4.6.2"
}
},
"Zomp.SyncMethodGenerator": {
"type": "Direct",
- "requested": "[2.0.56, )",
- "resolved": "2.0.56",
- "contentHash": "P1iMA1meu0uX8YcPWhkFnpzV3e+QFDDYMU6hMNyofS+pBlLgeVV7QevBmJmJ/mrxA1jujo3cqNaO1IapdfjOlA=="
+ "requested": "[2.0.66, )",
+ "resolved": "2.0.66",
+ "contentHash": "iYnF185851PEX5vnxrhUj2av30HoxvjUlPd0HVVuSAif7TCgc/Noc5xVf9jiLhMlI8Rt9QrJdef3KB8+opAJQA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
@@ -118,6 +119,11 @@
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
+ },
+ "System.ValueTuple": {
+ "type": "Transitive",
+ "resolved": "4.6.2",
+ "contentHash": "yQgmjfFximrNm9LIV3mL6T5MzjeC+epeE5rl4hXxAlYmxby7RM1dPSkIKXk9HNkl6G54h2JHOmLD46+Pey+IRg=="
}
},
".NETStandard,Version=v2.0": {
@@ -152,9 +158,9 @@
},
"Microsoft.VisualStudio.Threading.Analyzers": {
"type": "Direct",
- "requested": "[17.14.15, )",
- "resolved": "17.14.15",
- "contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
+ "requested": "[18.12.1, )",
+ "resolved": "18.12.1",
+ "contentHash": "sQt7NpnselT1gP1a7bTfhmfHYV0jbavst/8mOoWTMSjXhXzWPssqgBoIwrxNsgJUa7XQzD2raSRMAFfmlepxNw=="
},
"NETStandard.Library": {
"type": "Direct",
@@ -173,19 +179,19 @@
},
"System.Text.Encoding.CodePages": {
"type": "Direct",
- "requested": "[8.0.0, )",
- "resolved": "8.0.0",
- "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==",
+ "requested": "[10.0.12, )",
+ "resolved": "10.0.12",
+ "contentHash": "rt1G/LdKC6oc3xPyQhMn5vRTp9iTTt1BS9Dx/67TpW9KWgFJ39lyEz0y4CbDksx7iTZEfgG61UsQMoGEXEpF4A==",
"dependencies": {
- "System.Memory": "4.5.5",
- "System.Runtime.CompilerServices.Unsafe": "6.0.0"
+ "System.Memory": "4.6.3",
+ "System.Runtime.CompilerServices.Unsafe": "6.1.2"
}
},
"Zomp.SyncMethodGenerator": {
"type": "Direct",
- "requested": "[2.0.56, )",
- "resolved": "2.0.56",
- "contentHash": "P1iMA1meu0uX8YcPWhkFnpzV3e+QFDDYMU6hMNyofS+pBlLgeVV7QevBmJmJ/mrxA1jujo3cqNaO1IapdfjOlA=="
+ "requested": "[2.0.66, )",
+ "resolved": "2.0.66",
+ "contentHash": "iYnF185851PEX5vnxrhUj2av30HoxvjUlPd0HVVuSAif7TCgc/Noc5xVf9jiLhMlI8Rt9QrJdef3KB8+opAJQA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
@@ -282,9 +288,9 @@
},
"Microsoft.VisualStudio.Threading.Analyzers": {
"type": "Direct",
- "requested": "[17.14.15, )",
- "resolved": "17.14.15",
- "contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
+ "requested": "[18.12.1, )",
+ "resolved": "18.12.1",
+ "contentHash": "sQt7NpnselT1gP1a7bTfhmfHYV0jbavst/8mOoWTMSjXhXzWPssqgBoIwrxNsgJUa7XQzD2raSRMAFfmlepxNw=="
},
"PolySharp": {
"type": "Direct",
@@ -294,18 +300,18 @@
},
"System.Text.Encoding.CodePages": {
"type": "Direct",
- "requested": "[8.0.0, )",
- "resolved": "8.0.0",
- "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==",
+ "requested": "[10.0.12, )",
+ "resolved": "10.0.12",
+ "contentHash": "rt1G/LdKC6oc3xPyQhMn5vRTp9iTTt1BS9Dx/67TpW9KWgFJ39lyEz0y4CbDksx7iTZEfgG61UsQMoGEXEpF4A==",
"dependencies": {
- "System.Runtime.CompilerServices.Unsafe": "6.0.0"
+ "System.Runtime.CompilerServices.Unsafe": "6.1.2"
}
},
"Zomp.SyncMethodGenerator": {
"type": "Direct",
- "requested": "[2.0.56, )",
- "resolved": "2.0.56",
- "contentHash": "P1iMA1meu0uX8YcPWhkFnpzV3e+QFDDYMU6hMNyofS+pBlLgeVV7QevBmJmJ/mrxA1jujo3cqNaO1IapdfjOlA=="
+ "requested": "[2.0.66, )",
+ "resolved": "2.0.66",
+ "contentHash": "iYnF185851PEX5vnxrhUj2av30HoxvjUlPd0HVVuSAif7TCgc/Noc5xVf9jiLhMlI8Rt9QrJdef3KB8+opAJQA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
@@ -332,8 +338,8 @@
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
+ "resolved": "6.1.2",
+ "contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw=="
}
},
"net10.0": {
@@ -365,9 +371,9 @@
},
"Microsoft.VisualStudio.Threading.Analyzers": {
"type": "Direct",
- "requested": "[17.14.15, )",
- "resolved": "17.14.15",
- "contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
+ "requested": "[18.12.1, )",
+ "resolved": "18.12.1",
+ "contentHash": "sQt7NpnselT1gP1a7bTfhmfHYV0jbavst/8mOoWTMSjXhXzWPssqgBoIwrxNsgJUa7XQzD2raSRMAFfmlepxNw=="
},
"PolySharp": {
"type": "Direct",
@@ -377,9 +383,9 @@
},
"Zomp.SyncMethodGenerator": {
"type": "Direct",
- "requested": "[2.0.56, )",
- "resolved": "2.0.56",
- "contentHash": "P1iMA1meu0uX8YcPWhkFnpzV3e+QFDDYMU6hMNyofS+pBlLgeVV7QevBmJmJ/mrxA1jujo3cqNaO1IapdfjOlA=="
+ "requested": "[2.0.66, )",
+ "resolved": "2.0.66",
+ "contentHash": "iYnF185851PEX5vnxrhUj2av30HoxvjUlPd0HVVuSAif7TCgc/Noc5xVf9jiLhMlI8Rt9QrJdef3KB8+opAJQA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
@@ -428,9 +434,9 @@
},
"Microsoft.VisualStudio.Threading.Analyzers": {
"type": "Direct",
- "requested": "[17.14.15, )",
- "resolved": "17.14.15",
- "contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
+ "requested": "[18.12.1, )",
+ "resolved": "18.12.1",
+ "contentHash": "sQt7NpnselT1gP1a7bTfhmfHYV0jbavst/8mOoWTMSjXhXzWPssqgBoIwrxNsgJUa7XQzD2raSRMAFfmlepxNw=="
},
"PolySharp": {
"type": "Direct",
@@ -440,9 +446,9 @@
},
"Zomp.SyncMethodGenerator": {
"type": "Direct",
- "requested": "[2.0.56, )",
- "resolved": "2.0.56",
- "contentHash": "P1iMA1meu0uX8YcPWhkFnpzV3e+QFDDYMU6hMNyofS+pBlLgeVV7QevBmJmJ/mrxA1jujo3cqNaO1IapdfjOlA=="
+ "requested": "[2.0.66, )",
+ "resolved": "2.0.66",
+ "contentHash": "iYnF185851PEX5vnxrhUj2av30HoxvjUlPd0HVVuSAif7TCgc/Noc5xVf9jiLhMlI8Rt9QrJdef3KB8+opAJQA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
@@ -497,9 +503,9 @@
},
"Microsoft.VisualStudio.Threading.Analyzers": {
"type": "Direct",
- "requested": "[17.14.15, )",
- "resolved": "17.14.15",
- "contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
+ "requested": "[18.12.1, )",
+ "resolved": "18.12.1",
+ "contentHash": "sQt7NpnselT1gP1a7bTfhmfHYV0jbavst/8mOoWTMSjXhXzWPssqgBoIwrxNsgJUa7XQzD2raSRMAFfmlepxNw=="
},
"PolySharp": {
"type": "Direct",
@@ -509,9 +515,9 @@
},
"Zomp.SyncMethodGenerator": {
"type": "Direct",
- "requested": "[2.0.56, )",
- "resolved": "2.0.56",
- "contentHash": "P1iMA1meu0uX8YcPWhkFnpzV3e+QFDDYMU6hMNyofS+pBlLgeVV7QevBmJmJ/mrxA1jujo3cqNaO1IapdfjOlA=="
+ "requested": "[2.0.66, )",
+ "resolved": "2.0.66",
+ "contentHash": "iYnF185851PEX5vnxrhUj2av30HoxvjUlPd0HVVuSAif7TCgc/Noc5xVf9jiLhMlI8Rt9QrJdef3KB8+opAJQA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
diff --git a/tests/SharpCompress.AotSmoke/packages.lock.json b/tests/SharpCompress.AotSmoke/packages.lock.json
index 882d8334..7fb4c275 100644
--- a/tests/SharpCompress.AotSmoke/packages.lock.json
+++ b/tests/SharpCompress.AotSmoke/packages.lock.json
@@ -36,9 +36,9 @@
},
"Microsoft.VisualStudio.Threading.Analyzers": {
"type": "Direct",
- "requested": "[17.14.15, )",
- "resolved": "17.14.15",
- "contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
+ "requested": "[18.12.1, )",
+ "resolved": "18.12.1",
+ "contentHash": "sQt7NpnselT1gP1a7bTfhmfHYV0jbavst/8mOoWTMSjXhXzWPssqgBoIwrxNsgJUa7XQzD2raSRMAFfmlepxNw=="
},
"PolySharp": {
"type": "Direct",
@@ -48,9 +48,9 @@
},
"Zomp.SyncMethodGenerator": {
"type": "Direct",
- "requested": "[2.0.56, )",
- "resolved": "2.0.56",
- "contentHash": "P1iMA1meu0uX8YcPWhkFnpzV3e+QFDDYMU6hMNyofS+pBlLgeVV7QevBmJmJ/mrxA1jujo3cqNaO1IapdfjOlA=="
+ "requested": "[2.0.66, )",
+ "resolved": "2.0.66",
+ "contentHash": "iYnF185851PEX5vnxrhUj2av30HoxvjUlPd0HVVuSAif7TCgc/Noc5xVf9jiLhMlI8Rt9QrJdef3KB8+opAJQA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
diff --git a/tests/SharpCompress.Performance/packages.lock.json b/tests/SharpCompress.Performance/packages.lock.json
index eb03c8cd..a3d81fee 100644
--- a/tests/SharpCompress.Performance/packages.lock.json
+++ b/tests/SharpCompress.Performance/packages.lock.json
@@ -52,9 +52,9 @@
},
"Microsoft.VisualStudio.Threading.Analyzers": {
"type": "Direct",
- "requested": "[17.14.15, )",
- "resolved": "17.14.15",
- "contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
+ "requested": "[18.12.1, )",
+ "resolved": "18.12.1",
+ "contentHash": "sQt7NpnselT1gP1a7bTfhmfHYV0jbavst/8mOoWTMSjXhXzWPssqgBoIwrxNsgJUa7XQzD2raSRMAFfmlepxNw=="
},
"PolySharp": {
"type": "Direct",
@@ -64,9 +64,9 @@
},
"Zomp.SyncMethodGenerator": {
"type": "Direct",
- "requested": "[2.0.56, )",
- "resolved": "2.0.56",
- "contentHash": "P1iMA1meu0uX8YcPWhkFnpzV3e+QFDDYMU6hMNyofS+pBlLgeVV7QevBmJmJ/mrxA1jujo3cqNaO1IapdfjOlA=="
+ "requested": "[2.0.66, )",
+ "resolved": "2.0.66",
+ "contentHash": "iYnF185851PEX5vnxrhUj2av30HoxvjUlPd0HVVuSAif7TCgc/Noc5xVf9jiLhMlI8Rt9QrJdef3KB8+opAJQA=="
},
"BenchmarkDotNet.Annotations": {
"type": "Transitive",
diff --git a/tests/SharpCompress.Test/SharpCompress.Test.csproj b/tests/SharpCompress.Test/SharpCompress.Test.csproj
index 68ff64ae..a8769f29 100644
--- a/tests/SharpCompress.Test/SharpCompress.Test.csproj
+++ b/tests/SharpCompress.Test/SharpCompress.Test.csproj
@@ -12,6 +12,9 @@
AnyCPU
false
+
+ $(DefineConstants);STREAM_DISPOSEASYNC
+
$(DefineConstants);WINDOWS
diff --git a/tests/SharpCompress.Test/Streams/AsyncDisposalCompatibilityTests.cs b/tests/SharpCompress.Test/Streams/AsyncDisposalCompatibilityTests.cs
new file mode 100644
index 00000000..6407eda8
--- /dev/null
+++ b/tests/SharpCompress.Test/Streams/AsyncDisposalCompatibilityTests.cs
@@ -0,0 +1,53 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using SharpCompress.IO;
+using SharpCompress.Test.Mocks;
+using Xunit;
+
+namespace SharpCompress.Test.Streams;
+
+public class AsyncDisposalCompatibilityTests
+{
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async ValueTask SharpCompressStream_Forwards_Async_Disposal(bool seekable)
+ {
+ var source = new AsyncDisposeOnlyStream(seekable);
+ var wrapper = SharpCompressStream.Create(source, 128);
+ await ((IAsyncDisposable)wrapper).DisposeAsync().ConfigureAwait(false);
+ Assert.True(source.AsyncDisposed);
+ await ((IAsyncDisposable)wrapper).DisposeAsync().ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async ValueTask Stream_Disposal_Scope_Uses_Runtime_Async_Disposable()
+ {
+ var source = new AsyncDisposeOnlyStream(true);
+ Stream stream = source;
+ await using (stream.DisposeAsyncScope().ConfigureAwait(false)) { }
+ Assert.True(source.AsyncDisposed);
+ }
+
+ private sealed class AsyncDisposeOnlyStream(bool seekable) : AsyncOnlyStream(new MemoryStream())
+ {
+ public override bool CanSeek => seekable;
+ public bool AsyncDisposed { get; private set; }
+
+ public override ValueTask DisposeAsync()
+ {
+ AsyncDisposed = true;
+ return base.DisposeAsync();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !AsyncDisposed)
+ {
+ throw new NotSupportedException("Synchronous disposal is not supported.");
+ }
+ base.Dispose(disposing);
+ }
+ }
+}
diff --git a/tests/SharpCompress.Test/Streams/BufferPoolLifetimeTests.cs b/tests/SharpCompress.Test/Streams/BufferPoolLifetimeTests.cs
index dd26ba01..a0569d92 100644
--- a/tests/SharpCompress.Test/Streams/BufferPoolLifetimeTests.cs
+++ b/tests/SharpCompress.Test/Streams/BufferPoolLifetimeTests.cs
@@ -58,7 +58,7 @@ public class BufferPoolLifetimeTests
stream.Dispose();
}
stream.Dispose();
-#if !LEGACY_DOTNET
+#if STREAM_DISPOSEASYNC
await stream.DisposeAsync();
#else
await Task.CompletedTask;
diff --git a/tests/SharpCompress.Test/Streams/SharpCompressLifetimeTests.cs b/tests/SharpCompress.Test/Streams/SharpCompressLifetimeTests.cs
index df339154..b38ca2da 100644
--- a/tests/SharpCompress.Test/Streams/SharpCompressLifetimeTests.cs
+++ b/tests/SharpCompress.Test/Streams/SharpCompressLifetimeTests.cs
@@ -25,7 +25,7 @@ public class SharpCompressLifetimeTests
Assert.NotNull(ring.GetValue(stream));
if (asyncDispose)
{
-#if !LEGACY_DOTNET
+#if STREAM_DISPOSEASYNC
await Assert.ThrowsAsync(async () => await stream.DisposeAsync());
#else
Assert.Throws(() => stream.Dispose());
diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamEdgeAsyncTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamEdgeAsyncTest.cs
index 1c3b7d18..c2221d10 100644
--- a/tests/SharpCompress.Test/Streams/SharpCompressStreamEdgeAsyncTest.cs
+++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamEdgeAsyncTest.cs
@@ -9,7 +9,7 @@ namespace SharpCompress.Test.Streams;
public class SharpCompressStreamEdgeAsyncTest
{
-#if !LEGACY_DOTNET
+#if STREAM_DISPOSEASYNC
[Fact]
public async ValueTask DisposeAsync_WithLeaveStreamOpenTrue_DoesNotDisposeUnderlying()
diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamErrorAsyncTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamErrorAsyncTest.cs
index cf90a566..38020859 100644
--- a/tests/SharpCompress.Test/Streams/SharpCompressStreamErrorAsyncTest.cs
+++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamErrorAsyncTest.cs
@@ -49,7 +49,7 @@ public class SharpCompressStreamErrorAsyncTest
}
}
-#if !LEGACY_DOTNET
+#if STREAM_DISPOSEASYNC
[Fact]
public async ValueTask DisposeAsync_WithThrowOnDisposeTrue_ThrowsInvalidOperation()
{
diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamPassthroughAsyncTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamPassthroughAsyncTest.cs
index 521bac6a..862bd442 100644
--- a/tests/SharpCompress.Test/Streams/SharpCompressStreamPassthroughAsyncTest.cs
+++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamPassthroughAsyncTest.cs
@@ -84,7 +84,7 @@ public class SharpCompressStreamPassthroughAsyncTest
Assert.Equal(3, ms.Length);
}
-#if !LEGACY_DOTNET
+#if STREAM_DISPOSEASYNC
[Fact]
public async ValueTask CreateNonDisposing_DoesNotDisposeUnderlying_Async()
{
diff --git a/tests/SharpCompress.Test/Streams/WinzipAesCryptoStreamTests.cs b/tests/SharpCompress.Test/Streams/WinzipAesCryptoStreamTests.cs
index 8b879981..463b49f0 100644
--- a/tests/SharpCompress.Test/Streams/WinzipAesCryptoStreamTests.cs
+++ b/tests/SharpCompress.Test/Streams/WinzipAesCryptoStreamTests.cs
@@ -1,5 +1,6 @@
using System;
using System.Buffers.Binary;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
@@ -14,6 +15,120 @@ namespace SharpCompress.Test.Streams;
public class WinzipAesCryptoStreamTests
{
+ public static IEnumerable