Fully implement FInishAsync

This commit is contained in:
Adam Hathcock
2026-04-29 11:45:45 +01:00
parent afc2730548
commit 06a70fdd1e
8 changed files with 104 additions and 3 deletions

View File

@@ -49,6 +49,8 @@ public sealed partial class BZip2Stream : Stream, IFinishable
return bZip2Stream;
}
public ValueTask FinishAsync() => (stream as CBZip2OutputStream)?.FinishAsync() ?? default;
public void Finish() => (stream as CBZip2OutputStream)?.Finish();
protected override void Dispose(bool disposing)

View File

@@ -2088,7 +2088,7 @@ internal sealed class CBZip2OutputStream : Stream
/// Asynchronously finalizes the BZip2 compressed stream, flushing all pending data.
/// Writes the remaining compressed data to the underlying stream using async I/O.
/// </summary>
public async Task FinishAsync(CancellationToken cancellationToken = default)
public async ValueTask FinishAsync(CancellationToken cancellationToken = default)
{
if (finished)
{

View File

@@ -1,4 +1,5 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@@ -9,6 +10,82 @@ namespace SharpCompress.Compressors.LZMA;
public sealed partial class LZipStream
{
public static ValueTask<LZipStream> CreateAsync(
Stream stream,
CompressionMode mode,
bool leaveOpen = false,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
if (mode != CompressionMode.Compress)
{
return new ValueTask<LZipStream>(new LZipStream(stream, mode, leaveOpen));
}
// The LZMA encoder used for LZip currently finalizes synchronously, so the async
// creation path compresses to memory and writes the completed member asynchronously.
var compressedBuffer = new MemoryStream();
return new ValueTask<LZipStream>(
new LZipStream(compressedBuffer, mode, leaveOpen, stream, compressedBuffer)
);
}
public async ValueTask FinishAsync()
{
if (_finished)
{
return;
}
if (Mode == CompressionMode.Compress)
{
var crc32Stream = (Crc32Stream)_stream;
FinishWrappedStream(crc32Stream);
var compressedCount = _countingWritableSubStream.NotNull().BytesWritten;
var intBuf = new byte[8];
BinaryPrimitives.WriteUInt32LittleEndian(intBuf, crc32Stream.Crc);
await _countingWritableSubStream
.NotNull()
.WriteAsync(intBuf, 0, 4, CancellationToken.None)
.ConfigureAwait(false);
BinaryPrimitives.WriteInt64LittleEndian(intBuf, _writeCount);
await _countingWritableSubStream
.NotNull()
.WriteAsync(intBuf, 0, intBuf.Length, CancellationToken.None)
.ConfigureAwait(false);
// Total member size includes the 6-byte header and 20-byte trailer.
BinaryPrimitives.WriteUInt64LittleEndian(
intBuf,
(ulong)compressedCount + (ulong)(6 + 20)
);
await _countingWritableSubStream
.NotNull()
.WriteAsync(intBuf, 0, intBuf.Length, CancellationToken.None)
.ConfigureAwait(false);
if (_asyncCompressedBuffer is not null && _asyncFinalDestination is not null)
{
_asyncCompressedBuffer.Position = 0;
await _asyncCompressedBuffer
.CopyToAsync(_asyncFinalDestination, 81920, CancellationToken.None)
.ConfigureAwait(false);
}
}
_finished = true;
}
private static void FinishWrappedStream(Crc32Stream crc32Stream)
{
crc32Stream.WrappedStream.Dispose();
crc32Stream.Dispose();
}
/// <summary>
/// Asynchronously determines if the given stream is positioned at the start of a v1 LZip
/// file, as indicated by the ASCII characters "LZIP" and a version byte

View File

@@ -1,4 +1,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Compressors;
using SharpCompress.Compressors.LZMA;
@@ -19,6 +21,19 @@ public sealed class LZipCompressionProvider : CompressionProviderBase
return new LZipStream(destination, CompressionMode.Compress);
}
public override async ValueTask<Stream> CreateCompressStreamAsync(
Stream destination,
int compressionLevel,
CancellationToken cancellationToken = default
) =>
await LZipStream
.CreateAsync(
destination,
CompressionMode.Compress,
cancellationToken: cancellationToken
)
.ConfigureAwait(false);
public override Stream CreateDecompressStream(Stream source)
{
return new LZipStream(source, CompressionMode.Decompress);

View File

@@ -1,3 +1,5 @@
using System.Threading.Tasks;
namespace SharpCompress.Providers;
/// <summary>
@@ -16,4 +18,6 @@ public interface IFinishable
/// and writing format-specific trailer/footer bytes.
/// </summary>
void Finish();
ValueTask FinishAsync();
}

View File

@@ -29,7 +29,7 @@ public partial class TarWriter
}
if (OutputStream is IFinishable finishable)
{
finishable.Finish();
await finishable.FinishAsync().ConfigureAwait(false);
}
OutputStream?.Dispose();
// base.DisposeAsync() is a no-op since _isDisposed is already set

View File

@@ -19,6 +19,9 @@ public class AsyncOnlyStream(Stream stream) : Stream
set => _stream.Position = value;
}
public override Task FlushAsync(CancellationToken cancellationToken) =>
_stream.FlushAsync(cancellationToken);
public override void Flush() =>
throw new NotSupportedException("Synchronous Flush is not supported");

View File

@@ -65,7 +65,7 @@ public class TarWriterAsyncTests : WriterTests
{
using var stream = new MemoryStream();
using Stream content = File.OpenRead(Path.Combine(ORIGINAL_FILES_PATH, "jpg", "test.jpg"));
using (
await using (
var writer = new TarWriter(
new AsyncOnlyStream(stream),
new TarWriterOptions(CompressionType.None, finalizeArchive)