diff --git a/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.Async.cs b/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.Async.cs
new file mode 100644
index 00000000..eb158f25
--- /dev/null
+++ b/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.Async.cs
@@ -0,0 +1,706 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+#nullable disable
+
+namespace SharpCompress.Compressors.BZip2;
+
+internal sealed partial class CBZip2OutputStream
+{
+ private readonly byte[] bsAsyncWriteBuffer = new byte[1];
+
+ ///
+ /// Ensures the BZip2 stream header ('B', 'Z', 'h', blocksize) has been written
+ /// asynchronously before the first compressed byte is written.
+ ///
+ private async Task EnsureStreamHeaderWrittenAsync(CancellationToken cancellationToken)
+ {
+ if (!_streamHeaderWritten)
+ {
+ _streamHeaderWritten = true;
+ // Write 'B', 'Z', 'h' async, then set up bit buffer as Initialize() would.
+ // Initialize() calls BsPutUChar('h') then BsPutUChar('0'+N):
+ // - First call buffers 'h' (bsLive=8)
+ // - Second call flushes 'h' to stream, then buffers '0'+N (bsLive=8)
+ // So after Initialize(), stream has 'h' and bit buffer has '0'+N with bsLive=8.
+ var header = new byte[] { (byte)'B', (byte)'Z', (byte)'h' };
+ await bsStream
+ .WriteAsync(header, 0, header.Length, cancellationToken)
+ .ConfigureAwait(false);
+ // Replicate the bit buffer state that Initialize() leaves:
+ bytesOut = 1; // 'h' was written via BsW (Initialize increments bytesOut via BsW)
+ nBlocksRandomised = 0;
+ combinedCRC = 0;
+ bsBuff = (blockSize100k + '0') << 24;
+ bsLive = 8;
+ InitBlock();
+ }
+ }
+
+ public async ValueTask WriteByteAsync(byte bv, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ await EnsureStreamHeaderWrittenAsync(cancellationToken).ConfigureAwait(false);
+ var b = (256 + bv) % 256;
+ if (currentChar != -1)
+ {
+ if (currentChar == b)
+ {
+ runLength++;
+ if (runLength > 254)
+ {
+ await WriteRunAsync(cancellationToken).ConfigureAwait(false);
+ currentChar = -1;
+ runLength = 0;
+ }
+ }
+ else
+ {
+ await WriteRunAsync(cancellationToken).ConfigureAwait(false);
+ runLength = 1;
+ currentChar = b;
+ }
+ }
+ else
+ {
+ currentChar = b;
+ runLength++;
+ }
+ }
+
+ private async ValueTask WriteRunAsync(CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ if (last < allowableBlockSize)
+ {
+ inUse[currentChar] = true;
+ for (var i = 0; i < runLength; i++)
+ {
+ mCrc.UpdateCRC((char)currentChar);
+ }
+ switch (runLength)
+ {
+ case 1:
+ last++;
+ block[last + 1] = (char)currentChar;
+ break;
+ case 2:
+ last++;
+ block[last + 1] = (char)currentChar;
+ last++;
+ block[last + 1] = (char)currentChar;
+ break;
+ case 3:
+ last++;
+ block[last + 1] = (char)currentChar;
+ last++;
+ block[last + 1] = (char)currentChar;
+ last++;
+ block[last + 1] = (char)currentChar;
+ break;
+ default:
+ inUse[runLength - 4] = true;
+ last++;
+ block[last + 1] = (char)currentChar;
+ last++;
+ block[last + 1] = (char)currentChar;
+ last++;
+ block[last + 1] = (char)currentChar;
+ last++;
+ block[last + 1] = (char)currentChar;
+ last++;
+ block[last + 1] = (char)(runLength - 4);
+ break;
+ }
+ }
+ else
+ {
+ await EndBlockAsync(cancellationToken).ConfigureAwait(false);
+ InitBlock();
+ await WriteRunAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private async ValueTask EndBlockAsync(CancellationToken cancellationToken)
+ {
+ // Skip block processing for empty input (no data written)
+ if (last < 0)
+ {
+ return;
+ }
+
+ blockCRC = mCrc.GetFinalCRC();
+ combinedCRC = (combinedCRC << 1) | (int)(((uint)combinedCRC) >> 31);
+ combinedCRC ^= blockCRC;
+
+ /* sort the block and establish posn of original string */
+ DoReversibleTransformation();
+
+ await BsPutUCharAsync(0x31, cancellationToken).ConfigureAwait(false);
+ await BsPutUCharAsync(0x41, cancellationToken).ConfigureAwait(false);
+ await BsPutUCharAsync(0x59, cancellationToken).ConfigureAwait(false);
+ await BsPutUCharAsync(0x26, cancellationToken).ConfigureAwait(false);
+ await BsPutUCharAsync(0x53, cancellationToken).ConfigureAwait(false);
+ await BsPutUCharAsync(0x59, cancellationToken).ConfigureAwait(false);
+
+ await BsPutintAsync(blockCRC, cancellationToken).ConfigureAwait(false);
+
+ if (blockRandomised)
+ {
+ await BsWAsync(1, 1, cancellationToken).ConfigureAwait(false);
+ nBlocksRandomised++;
+ }
+ else
+ {
+ await BsWAsync(1, 0, cancellationToken).ConfigureAwait(false);
+ }
+
+ await MoveToFrontCodeAndSendAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ private async ValueTask EndCompressionAsync(CancellationToken cancellationToken)
+ {
+ await BsPutUCharAsync(0x17, cancellationToken).ConfigureAwait(false);
+ await BsPutUCharAsync(0x72, cancellationToken).ConfigureAwait(false);
+ await BsPutUCharAsync(0x45, cancellationToken).ConfigureAwait(false);
+ await BsPutUCharAsync(0x38, cancellationToken).ConfigureAwait(false);
+ await BsPutUCharAsync(0x50, cancellationToken).ConfigureAwait(false);
+ await BsPutUCharAsync(0x90, cancellationToken).ConfigureAwait(false);
+
+ await BsPutintAsync(combinedCRC, cancellationToken).ConfigureAwait(false);
+
+ await BsFinishedWithStreamAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ private async ValueTask BsFinishedWithStreamAsync(CancellationToken cancellationToken)
+ {
+ while (bsLive > 0)
+ {
+ var ch = bsBuff >> 24;
+ bsAsyncWriteBuffer[0] = (byte)ch;
+ await bsStream
+ .WriteAsync(bsAsyncWriteBuffer, 0, 1, cancellationToken)
+ .ConfigureAwait(false);
+ bsBuff <<= 8;
+ bsLive -= 8;
+ bytesOut++;
+ }
+ }
+
+ private async ValueTask BsWAsync(int n, int v, CancellationToken cancellationToken)
+ {
+ while (bsLive >= 8)
+ {
+ var ch = bsBuff >> 24;
+ bsAsyncWriteBuffer[0] = (byte)ch;
+ await bsStream
+ .WriteAsync(bsAsyncWriteBuffer, 0, 1, cancellationToken)
+ .ConfigureAwait(false);
+ bsBuff <<= 8;
+ bsLive -= 8;
+ bytesOut++;
+ }
+ bsBuff |= v << (32 - bsLive - n);
+ bsLive += n;
+ }
+
+ private ValueTask BsPutUCharAsync(int c, CancellationToken cancellationToken) =>
+ BsWAsync(8, c, cancellationToken);
+
+ private async ValueTask BsPutintAsync(int u, CancellationToken cancellationToken)
+ {
+ await BsWAsync(8, (u >> 24) & 0xff, cancellationToken).ConfigureAwait(false);
+ await BsWAsync(8, (u >> 16) & 0xff, cancellationToken).ConfigureAwait(false);
+ await BsWAsync(8, (u >> 8) & 0xff, cancellationToken).ConfigureAwait(false);
+ await BsWAsync(8, u & 0xff, cancellationToken).ConfigureAwait(false);
+ }
+
+ private ValueTask BsPutIntVSAsync(int numBits, int c, CancellationToken cancellationToken) =>
+ BsWAsync(numBits, c, cancellationToken);
+
+ private async ValueTask SendMTFValuesAsync(CancellationToken cancellationToken)
+ {
+ var len = CBZip2InputStream.InitCharArray(
+ BZip2Constants.N_GROUPS,
+ BZip2Constants.MAX_ALPHA_SIZE
+ );
+
+ int v,
+ t,
+ i,
+ j,
+ gs,
+ ge,
+ totc,
+ bt,
+ bc,
+ iter;
+ int nSelectors = 0,
+ alphaSize,
+ minLen,
+ maxLen,
+ selCtr;
+ int nGroups; //, nBytes;
+
+ alphaSize = nInUse + 2;
+ for (t = 0; t < BZip2Constants.N_GROUPS; t++)
+ {
+ for (v = 0; v < alphaSize; v++)
+ {
+ len[t][v] = (char)GREATER_ICOST;
+ }
+ }
+
+ if (nMTF <= 0)
+ {
+ Panic();
+ }
+
+ if (nMTF < 200)
+ {
+ nGroups = 2;
+ }
+ else if (nMTF < 600)
+ {
+ nGroups = 3;
+ }
+ else if (nMTF < 1200)
+ {
+ nGroups = 4;
+ }
+ else if (nMTF < 2400)
+ {
+ nGroups = 5;
+ }
+ else
+ {
+ nGroups = 6;
+ }
+
+ {
+ int nPart,
+ remF,
+ tFreq,
+ aFreq;
+
+ nPart = nGroups;
+ remF = nMTF;
+ gs = 0;
+ while (nPart > 0)
+ {
+ tFreq = remF / nPart;
+ ge = gs - 1;
+ aFreq = 0;
+ while (aFreq < tFreq && ge < alphaSize - 1)
+ {
+ ge++;
+ aFreq += mtfFreq[ge];
+ }
+
+ if (ge > gs && nPart != nGroups && nPart != 1 && ((nGroups - nPart) % 2 == 1))
+ {
+ aFreq -= mtfFreq[ge];
+ ge--;
+ }
+
+ for (v = 0; v < alphaSize; v++)
+ {
+ if (v >= gs && v <= ge)
+ {
+ len[nPart - 1][v] = (char)LESSER_ICOST;
+ }
+ else
+ {
+ len[nPart - 1][v] = (char)GREATER_ICOST;
+ }
+ }
+
+ nPart--;
+ gs = ge + 1;
+ remF -= aFreq;
+ }
+ }
+
+ var rfreq = CBZip2InputStream.InitIntArray(
+ BZip2Constants.N_GROUPS,
+ BZip2Constants.MAX_ALPHA_SIZE
+ );
+ var fave = new int[BZip2Constants.N_GROUPS];
+ var cost = new short[BZip2Constants.N_GROUPS];
+ for (iter = 0; iter < BZip2Constants.N_ITERS; iter++)
+ {
+ for (t = 0; t < nGroups; t++)
+ {
+ fave[t] = 0;
+ }
+
+ for (t = 0; t < nGroups; t++)
+ {
+ for (v = 0; v < alphaSize; v++)
+ {
+ rfreq[t][v] = 0;
+ }
+ }
+
+ nSelectors = 0;
+ totc = 0;
+ gs = 0;
+ while (true)
+ {
+ if (gs >= nMTF)
+ {
+ break;
+ }
+ ge = gs + BZip2Constants.G_SIZE - 1;
+ if (ge >= nMTF)
+ {
+ ge = nMTF - 1;
+ }
+
+ for (t = 0; t < nGroups; t++)
+ {
+ cost[t] = 0;
+ }
+
+ if (nGroups == 6)
+ {
+ short cost0,
+ cost1,
+ cost2,
+ cost3,
+ cost4,
+ cost5;
+ cost0 = cost1 = cost2 = cost3 = cost4 = cost5 = 0;
+ for (i = gs; i <= ge; i++)
+ {
+ var icv = szptr[i];
+ cost0 += (short)len[0][icv];
+ cost1 += (short)len[1][icv];
+ cost2 += (short)len[2][icv];
+ cost3 += (short)len[3][icv];
+ cost4 += (short)len[4][icv];
+ cost5 += (short)len[5][icv];
+ }
+ cost[0] = cost0;
+ cost[1] = cost1;
+ cost[2] = cost2;
+ cost[3] = cost3;
+ cost[4] = cost4;
+ cost[5] = cost5;
+ }
+ else
+ {
+ for (i = gs; i <= ge; i++)
+ {
+ var icv = szptr[i];
+ for (t = 0; t < nGroups; t++)
+ {
+ cost[t] += (short)len[t][icv];
+ }
+ }
+ }
+
+ bc = 999999999;
+ bt = -1;
+ for (t = 0; t < nGroups; t++)
+ {
+ if (cost[t] < bc)
+ {
+ bc = cost[t];
+ bt = t;
+ }
+ }
+ ;
+ totc += bc;
+ fave[bt]++;
+ selector[nSelectors] = (char)bt;
+ nSelectors++;
+
+ for (i = gs; i <= ge; i++)
+ {
+ rfreq[bt][szptr[i]]++;
+ }
+
+ gs = ge + 1;
+ }
+
+ for (t = 0; t < nGroups; t++)
+ {
+ HbMakeCodeLengths(len[t], rfreq[t], alphaSize, 20);
+ }
+ }
+
+ rfreq = null;
+ fave = null;
+ cost = null;
+
+ if (!(nGroups < 8))
+ {
+ Panic();
+ }
+ if (!(nSelectors < 32768 && nSelectors <= (2 + (900000 / BZip2Constants.G_SIZE))))
+ {
+ Panic();
+ }
+
+ {
+ var pos = new char[BZip2Constants.N_GROUPS];
+ char ll_i,
+ tmp2,
+ tmp;
+ for (i = 0; i < nGroups; i++)
+ {
+ pos[i] = (char)i;
+ }
+ for (i = 0; i < nSelectors; i++)
+ {
+ ll_i = selector[i];
+ j = 0;
+ tmp = pos[j];
+ while (ll_i != tmp)
+ {
+ j++;
+ tmp2 = tmp;
+ tmp = pos[j];
+ pos[j] = tmp2;
+ }
+ pos[0] = tmp;
+ selectorMtf[i] = (char)j;
+ }
+ }
+
+ var code = CBZip2InputStream.InitIntArray(
+ BZip2Constants.N_GROUPS,
+ BZip2Constants.MAX_ALPHA_SIZE
+ );
+
+ for (t = 0; t < nGroups; t++)
+ {
+ minLen = 32;
+ maxLen = 0;
+ for (i = 0; i < alphaSize; i++)
+ {
+ if (len[t][i] > maxLen)
+ {
+ maxLen = len[t][i];
+ }
+ if (len[t][i] < minLen)
+ {
+ minLen = len[t][i];
+ }
+ }
+ if (maxLen > 20)
+ {
+ Panic();
+ }
+ if (minLen < 1)
+ {
+ Panic();
+ }
+ HbAssignCodes(code[t], len[t], minLen, maxLen, alphaSize);
+ }
+
+ {
+ var inUse16 = new bool[16];
+ for (i = 0; i < 16; i++)
+ {
+ inUse16[i] = false;
+ for (j = 0; j < 16; j++)
+ {
+ if (inUse[(i * 16) + j])
+ {
+ inUse16[i] = true;
+ }
+ }
+ }
+
+ for (i = 0; i < 16; i++)
+ {
+ if (inUse16[i])
+ {
+ await BsWAsync(1, 1, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ await BsWAsync(1, 0, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ for (i = 0; i < 16; i++)
+ {
+ if (inUse16[i])
+ {
+ for (j = 0; j < 16; j++)
+ {
+ if (inUse[(i * 16) + j])
+ {
+ await BsWAsync(1, 1, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ await BsWAsync(1, 0, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ }
+ }
+ }
+
+ await BsWAsync(3, nGroups, cancellationToken).ConfigureAwait(false);
+ await BsWAsync(15, nSelectors, cancellationToken).ConfigureAwait(false);
+ for (i = 0; i < nSelectors; i++)
+ {
+ for (j = 0; j < selectorMtf[i]; j++)
+ {
+ await BsWAsync(1, 1, cancellationToken).ConfigureAwait(false);
+ }
+ await BsWAsync(1, 0, cancellationToken).ConfigureAwait(false);
+ }
+
+ for (t = 0; t < nGroups; t++)
+ {
+ int curr = len[t][0];
+ await BsWAsync(5, curr, cancellationToken).ConfigureAwait(false);
+ for (i = 0; i < alphaSize; i++)
+ {
+ while (curr < len[t][i])
+ {
+ await BsWAsync(2, 2, cancellationToken).ConfigureAwait(false);
+ curr++;
+ }
+ while (curr > len[t][i])
+ {
+ await BsWAsync(2, 3, cancellationToken).ConfigureAwait(false);
+ curr--;
+ }
+ await BsWAsync(1, 0, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ selCtr = 0;
+ gs = 0;
+ while (true)
+ {
+ if (gs >= nMTF)
+ {
+ break;
+ }
+ ge = gs + BZip2Constants.G_SIZE - 1;
+ if (ge >= nMTF)
+ {
+ ge = nMTF - 1;
+ }
+ for (i = gs; i <= ge; i++)
+ {
+ await BsWAsync(
+ len[selector[selCtr]][szptr[i]],
+ code[selector[selCtr]][szptr[i]],
+ cancellationToken
+ )
+ .ConfigureAwait(false);
+ }
+
+ gs = ge + 1;
+ selCtr++;
+ }
+ if (!(selCtr == nSelectors))
+ {
+ Panic();
+ }
+ }
+
+ private async ValueTask MoveToFrontCodeAndSendAsync(CancellationToken cancellationToken)
+ {
+ await BsPutIntVSAsync(24, origPtr, cancellationToken).ConfigureAwait(false);
+ GenerateMTFValues();
+ await SendMTFValuesAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteAsync(
+ byte[] buffer,
+ int offset,
+ int count,
+ CancellationToken cancellationToken = default
+ )
+ {
+ await EnsureStreamHeaderWrittenAsync(cancellationToken).ConfigureAwait(false);
+ for (var k = 0; k < count; ++k)
+ {
+ await WriteByteAsync(buffer[k + offset], cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+#if !LEGACY_DOTNET
+ public override async ValueTask WriteAsync(
+ ReadOnlyMemory buffer,
+ CancellationToken cancellationToken = default
+ )
+ {
+ await EnsureStreamHeaderWrittenAsync(cancellationToken).ConfigureAwait(false);
+ for (var k = 0; k < buffer.Length; ++k)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var value = buffer.Span[k];
+ await WriteByteAsync(value, cancellationToken).ConfigureAwait(false);
+ }
+ }
+#endif
+
+ ///
+ /// Asynchronously finalizes the BZip2 compressed stream, flushing all pending data.
+ /// Writes the remaining compressed data to the underlying stream using async I/O.
+ ///
+ public async ValueTask FinishAsync(CancellationToken cancellationToken = default)
+ {
+ if (finished)
+ {
+ return;
+ }
+
+ await EnsureStreamHeaderWrittenAsync(cancellationToken).ConfigureAwait(false);
+
+ if (runLength > 0)
+ {
+ await WriteRunAsync(cancellationToken).ConfigureAwait(false);
+ }
+ currentChar = -1;
+ await EndBlockAsync(cancellationToken).ConfigureAwait(false);
+ await EndCompressionAsync(cancellationToken).ConfigureAwait(false);
+ finished = true;
+ await bsStream.FlushAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+#if !LEGACY_DOTNET || NETSTANDARD2_1
+ public override async ValueTask DisposeAsync()
+#else
+ public async ValueTask DisposeAsync()
+#endif
+ {
+ if (disposed)
+ {
+ return;
+ }
+
+ await FinishAsync().ConfigureAwait(false);
+ disposed = true;
+ if (!leaveOpen && bsStream is not null)
+ {
+ if (bsStream is IAsyncDisposable asyncDisposable)
+ {
+ await asyncDisposable.DisposeAsync().ConfigureAwait(false);
+ }
+ else
+ {
+ bsStream.Dispose();
+ }
+ }
+ bsStream = null;
+
+#if !LEGACY_DOTNET || NETSTANDARD2_1
+ await base.DisposeAsync().ConfigureAwait(false);
+#else
+ await Task.CompletedTask.ConfigureAwait(false);
+#endif
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs b/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs
index 4c1282e9..465909a4 100644
--- a/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs
+++ b/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs
@@ -1,7 +1,5 @@
using System;
using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
/*
* Copyright 2001,2004-2005 The Apache Software Foundation
*
@@ -39,7 +37,7 @@ namespace SharpCompress.Compressors.BZip2;
* start of the BZIP2 stream to make it compatible with other PGP programs.
*/
-internal sealed class CBZip2OutputStream : Stream
+internal sealed partial class CBZip2OutputStream : Stream
{
private const int SETMASK = (1 << 21);
private const int CLEARMASK = (~SETMASK);
@@ -373,34 +371,6 @@ internal sealed class CBZip2OutputStream : Stream
}
}
- ///
- /// Ensures the BZip2 stream header ('B', 'Z', 'h', blocksize) has been written
- /// asynchronously before the first compressed byte is written.
- ///
- private async Task EnsureStreamHeaderWrittenAsync(CancellationToken cancellationToken)
- {
- if (!_streamHeaderWritten)
- {
- _streamHeaderWritten = true;
- // Write 'B', 'Z', 'h' async, then set up bit buffer as Initialize() would.
- // Initialize() calls BsPutUChar('h') then BsPutUChar('0'+N):
- // - First call buffers 'h' (bsLive=8)
- // - Second call flushes 'h' to stream, then buffers '0'+N (bsLive=8)
- // So after Initialize(), stream has 'h' and bit buffer has '0'+N with bsLive=8.
- var header = new byte[] { (byte)'B', (byte)'Z', (byte)'h' };
- await bsStream
- .WriteAsync(header, 0, header.Length, cancellationToken)
- .ConfigureAwait(false);
- // Replicate the bit buffer state that Initialize() leaves:
- bytesOut = 1; // 'h' was written via BsW (Initialize increments bytesOut via BsW)
- nBlocksRandomised = 0;
- combinedCRC = 0;
- bsBuff = (blockSize100k + '0') << 24;
- bsLive = 8;
- InitBlock();
- }
- }
-
public override void WriteByte(byte bv)
{
EnsureStreamHeaderWritten();
@@ -2054,111 +2024,6 @@ internal sealed class CBZip2OutputStream : Stream
}
}
- public override async Task WriteAsync(
- byte[] buffer,
- int offset,
- int count,
- CancellationToken cancellationToken = default
- )
- {
- await EnsureStreamHeaderWrittenAsync(cancellationToken).ConfigureAwait(false);
- for (var k = 0; k < count; ++k)
- {
- cancellationToken.ThrowIfCancellationRequested();
- WriteByte(buffer[k + offset]);
- }
- }
-
-#if !LEGACY_DOTNET
- public override async ValueTask WriteAsync(
- ReadOnlyMemory buffer,
- CancellationToken cancellationToken = default
- )
- {
- await EnsureStreamHeaderWrittenAsync(cancellationToken).ConfigureAwait(false);
- for (var k = 0; k < buffer.Length; ++k)
- {
- cancellationToken.ThrowIfCancellationRequested();
- WriteByte(buffer.Span[k]);
- }
- }
-#endif
-
- ///
- /// Asynchronously finalizes the BZip2 compressed stream, flushing all pending data.
- /// Writes the remaining compressed data to the underlying stream using async I/O.
- ///
- public async ValueTask FinishAsync(CancellationToken cancellationToken = default)
- {
- if (finished)
- {
- return;
- }
-
- await EnsureStreamHeaderWrittenAsync(cancellationToken).ConfigureAwait(false);
-
- // Run the CPU-bound finalization synchronously into a temporary MemoryStream
- // to avoid synchronous writes to the potentially async-only underlying stream.
- using var tempStream = new MemoryStream();
- var realStream = bsStream;
- bsStream = tempStream;
-
- try
- {
- if (runLength > 0)
- {
- WriteRun();
- }
- currentChar = -1;
- EndBlock();
- EndCompression();
- finished = true;
- }
- finally
- {
- bsStream = realStream;
- }
-
- // Async-copy the buffered compressed data to the real stream
- tempStream.Position = 0;
- await tempStream.CopyToAsync(realStream, 81920, cancellationToken).ConfigureAwait(false);
- await realStream.FlushAsync(cancellationToken).ConfigureAwait(false);
- }
-
-#if !LEGACY_DOTNET || NETSTANDARD2_1
- public override async ValueTask DisposeAsync()
-#else
- public async ValueTask DisposeAsync()
-#endif
- {
- if (disposed)
- {
- return;
- }
-
- await FinishAsync().ConfigureAwait(false);
- disposed = true;
- if (!leaveOpen && bsStream is not null)
- {
- if (bsStream is IAsyncDisposable asyncDisposable)
- {
- await asyncDisposable.DisposeAsync().ConfigureAwait(false);
- }
- else
- {
- bsStream.Dispose();
- }
- }
- bsStream = null;
-
-#if !LEGACY_DOTNET || NETSTANDARD2_1
- await base.DisposeAsync().ConfigureAwait(false);
-#else
- await Task.CompletedTask.ConfigureAwait(false);
-#endif
- GC.SuppressFinalize(this);
- }
-
public override bool CanRead => false;
public override bool CanSeek => false;
diff --git a/src/SharpCompress/Providers/Default/BZip2CompressionProvider.cs b/src/SharpCompress/Providers/Default/BZip2CompressionProvider.cs
index aa8bf557..8ff07fb7 100644
--- a/src/SharpCompress/Providers/Default/BZip2CompressionProvider.cs
+++ b/src/SharpCompress/Providers/Default/BZip2CompressionProvider.cs
@@ -22,6 +22,18 @@ public sealed class BZip2CompressionProvider : CompressionProviderBase
return BZip2Stream.Create(destination, CompressionMode.Compress, false);
}
+ public override async ValueTask CreateCompressStreamAsync(
+ Stream destination,
+ int compressionLevel,
+ CancellationToken cancellationToken = default
+ )
+ {
+ // BZip2 doesn't use compressionLevel parameter in this implementation
+ return await BZip2Stream
+ .CreateAsync(destination, CompressionMode.Compress, false, false, cancellationToken)
+ .ConfigureAwait(false);
+ }
+
public override Stream CreateDecompressStream(Stream source)
{
return BZip2Stream.Create(source, CompressionMode.Decompress, false);