more 7z async

This commit is contained in:
Adam Hathcock
2026-01-29 10:13:55 +00:00
parent e2cb9f39ab
commit b994f0ab55
13 changed files with 717 additions and 36 deletions

View File

@@ -129,7 +129,7 @@ public partial class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, Sev
DiagnosticsEnabled ? _currentFolderStream : null;
internal SevenZipReader(ReaderOptions readerOptions, SevenZipArchive archive)
: base(readerOptions, ArchiveType.SevenZip) => this._archive = archive;
: base(readerOptions, ArchiveType.SevenZip, false) => this._archive = archive;
public override SevenZipVolume Volume => _archive.Volumes.Single();

View File

@@ -12,8 +12,7 @@ public class SevenZipArchiveEntry : SevenZipEntry, IArchiveEntry
public Stream OpenEntryStream() => FilePart.GetCompressedStream();
public ValueTask<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default) =>
new(OpenEntryStream());
public async ValueTask<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default) => (await FilePart.GetCompressedStreamAsync(cancellationToken)).NotNull();
public IArchive Archive { get; }

View File

@@ -0,0 +1,40 @@
#nullable disable
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Compressors.LZMA;
using SharpCompress.Compressors.LZMA.Utilites;
namespace SharpCompress.Common.SevenZip;
internal sealed partial class ArchiveDatabase
{
internal async ValueTask<Stream> GetFolderStreamAsync(
Stream stream,
CFolder folder,
IPasswordProvider pw,
CancellationToken cancellationToken
)
{
var packStreamIndex = folder._firstPackStreamId;
var folderStartPackPos = GetFolderStreamPos(folder, 0);
var count = folder._packStreams.Count;
var packSizes = new long[count];
for (var j = 0; j < count; j++)
{
packSizes[j] = _packSizes[packStreamIndex + j];
}
return await DecoderStreamHelper
.CreateDecoderStreamAsync(
stream,
folderStartPackPos,
packSizes,
folder,
pw,
cancellationToken
)
.ConfigureAwait(false);
}
}

View File

@@ -1,4 +1,4 @@
#nullable disable
#nullable disable
using System;
using System.Collections.Generic;
@@ -8,7 +8,7 @@ using SharpCompress.Compressors.LZMA.Utilites;
namespace SharpCompress.Common.SevenZip;
internal class ArchiveDatabase
internal partial class ArchiveDatabase
{
internal byte _majorVersion;
internal byte _minorVersion;

View File

@@ -1,6 +1,7 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@@ -125,10 +126,12 @@ internal sealed partial class ArchiveReader
throw new InvalidOperationException();
}
var dataVector = ReadAndDecodePackedStreams(
db._startPositionAfterHeader,
db.PasswordProvider
);
var dataVector = await ReadAndDecodePackedStreamsAsync(
db._startPositionAfterHeader,
db.PasswordProvider,
cancellationToken
)
.ConfigureAwait(false);
// compressed header without content is odd but ok
if (dataVector.Count == 0)
@@ -150,9 +153,428 @@ internal sealed partial class ArchiveReader
}
}
ReadHeader(db, db.PasswordProvider);
await ReadHeaderAsync(db, db.PasswordProvider, cancellationToken).ConfigureAwait(false);
}
db.Fill();
return db;
}
private async ValueTask<List<byte[]>> ReadAndDecodePackedStreamsAsync(
long baseOffset,
IPasswordProvider pass,
CancellationToken cancellationToken
)
{
#if DEBUG
Log.WriteLine("-- ReadAndDecodePackedStreamsAsync --");
Log.PushIndent();
#endif
try
{
ReadStreamsInfo(
null,
out var dataStartPos,
out var packSizes,
out var packCrCs,
out var folders,
out var numUnpackStreamsInFolders,
out var unpackSizes,
out var digests
);
dataStartPos += baseOffset;
var dataVector = new List<byte[]>(folders.Count);
var packIndex = 0;
foreach (var folder in folders)
{
var oldDataStartPos = dataStartPos;
var myPackSizes = new long[folder._packStreams.Count];
for (var i = 0; i < myPackSizes.Length; i++)
{
var packSize = packSizes[packIndex + i];
myPackSizes[i] = packSize;
dataStartPos += packSize;
}
var outStream = await DecoderStreamHelper
.CreateDecoderStreamAsync(
_stream,
oldDataStartPos,
myPackSizes,
folder,
pass,
cancellationToken
)
.ConfigureAwait(false);
var unpackSize = checked((int)folder.GetUnpackSize());
var data = new byte[unpackSize];
await outStream
.ReadExactAsync(data, 0, data.Length, cancellationToken)
.ConfigureAwait(false);
if (outStream.ReadByte() >= 0)
{
throw new InvalidFormatException("Decoded stream is longer than expected.");
}
dataVector.Add(data);
if (folder.UnpackCrcDefined)
{
if (
Crc.Finish(Crc.Update(Crc.INIT_CRC, data, 0, unpackSize))
!= folder._unpackCrc
)
{
throw new InvalidFormatException(
"Decoded stream does not match expected CRC."
);
}
}
}
return dataVector;
}
finally
{
#if DEBUG
Log.PopIndent();
#endif
}
}
private async ValueTask ReadHeaderAsync(
ArchiveDatabase db,
IPasswordProvider getTextPassword,
CancellationToken cancellationToken
)
{
#if DEBUG
Log.WriteLine("-- ReadHeaderAsync --");
Log.PushIndent();
#endif
try
{
var type = ReadId();
if (type == BlockType.ArchiveProperties)
{
ReadArchiveProperties();
type = ReadId();
}
List<byte[]> dataVector = null;
if (type == BlockType.AdditionalStreamsInfo)
{
dataVector = await ReadAndDecodePackedStreamsAsync(
db._startPositionAfterHeader,
getTextPassword,
cancellationToken
)
.ConfigureAwait(false);
type = ReadId();
}
List<long> unpackSizes;
List<uint?> digests;
if (type == BlockType.MainStreamsInfo)
{
ReadStreamsInfo(
dataVector,
out db._dataStartPosition,
out db._packSizes,
out db._packCrCs,
out db._folders,
out db._numUnpackStreamsVector,
out unpackSizes,
out digests
);
db._dataStartPosition += db._startPositionAfterHeader;
type = ReadId();
}
else
{
unpackSizes = new List<long>(db._folders.Count);
digests = new List<uint?>(db._folders.Count);
db._numUnpackStreamsVector = new List<int>(db._folders.Count);
for (var i = 0; i < db._folders.Count; i++)
{
var folder = db._folders[i];
unpackSizes.Add(folder.GetUnpackSize());
digests.Add(folder._unpackCrc);
db._numUnpackStreamsVector.Add(1);
}
}
db._files.Clear();
if (type == BlockType.End)
{
return;
}
if (type != BlockType.FilesInfo)
{
throw new InvalidOperationException();
}
var numFiles = ReadNum();
#if DEBUG
Log.WriteLine("NumFiles: " + numFiles);
#endif
db._files = new List<CFileItem>(numFiles);
for (var i = 0; i < numFiles; i++)
{
db._files.Add(new CFileItem());
}
var emptyStreamVector = new BitVector(numFiles);
BitVector emptyFileVector = null;
BitVector antiFileVector = null;
var numEmptyStreams = 0;
for (; ; )
{
type = ReadId();
if (type == BlockType.End)
{
break;
}
var size = checked((long)ReadNumber());
var oldPos = _currentReader.Offset;
switch (type)
{
case BlockType.Name:
using (var streamSwitch = new CStreamSwitch())
{
streamSwitch.Set(this, dataVector);
#if DEBUG
Log.Write("FileNames:");
#endif
for (var i = 0; i < db._files.Count; i++)
{
db._files[i].Name = _currentReader.ReadString();
#if DEBUG
Log.Write(" " + db._files[i].Name);
#endif
}
#if DEBUG
Log.WriteLine();
#endif
}
break;
case BlockType.WinAttributes:
#if DEBUG
Log.Write("WinAttributes:");
#endif
ReadAttributeVector(
dataVector,
numFiles,
delegate(int i, uint? attr)
{
db._files[i].ExtendedAttrib = attr;
if (attr.HasValue && (attr.Value >> 16) != 0)
{
attr = attr.Value & 0x7FFFu;
}
db._files[i].Attrib = attr;
#if DEBUG
Log.Write(
" " + (attr.HasValue ? attr.Value.ToString("x8") : "n/a")
);
#endif
}
);
#if DEBUG
Log.WriteLine();
#endif
break;
case BlockType.EmptyStream:
emptyStreamVector = ReadBitVector(numFiles);
#if DEBUG
Log.Write("EmptyStream: ");
#endif
for (var i = 0; i < emptyStreamVector.Length; i++)
{
if (emptyStreamVector[i])
{
#if DEBUG
Log.Write("x");
#endif
numEmptyStreams++;
}
else
{
#if DEBUG
Log.Write(".");
#endif
}
}
#if DEBUG
Log.WriteLine();
#endif
emptyFileVector = new BitVector(numEmptyStreams);
antiFileVector = new BitVector(numEmptyStreams);
break;
case BlockType.EmptyFile:
emptyFileVector = ReadBitVector(numEmptyStreams);
#if DEBUG
Log.Write("EmptyFile: ");
for (var i = 0; i < numEmptyStreams; i++)
{
Log.Write(emptyFileVector[i] ? "x" : ".");
}
Log.WriteLine();
#endif
break;
case BlockType.Anti:
antiFileVector = ReadBitVector(numEmptyStreams);
#if DEBUG
Log.Write("Anti: ");
for (var i = 0; i < numEmptyStreams; i++)
{
Log.Write(antiFileVector[i] ? "x" : ".");
}
Log.WriteLine();
#endif
break;
case BlockType.StartPos:
#if DEBUG
Log.Write("StartPos:");
#endif
ReadNumberVector(
dataVector,
numFiles,
delegate(int i, long? startPos)
{
db._files[i].StartPos = startPos;
#if DEBUG
Log.Write(
" " + (startPos.HasValue ? startPos.Value.ToString() : "n/a")
);
#endif
}
);
#if DEBUG
Log.WriteLine();
#endif
break;
case BlockType.CTime:
#if DEBUG
Log.Write("CTime:");
#endif
ReadDateTimeVector(
dataVector,
numFiles,
delegate(int i, DateTime? time)
{
db._files[i].CTime = time;
#if DEBUG
Log.Write(" " + (time.HasValue ? time.Value.ToString() : "n/a"));
#endif
}
);
#if DEBUG
Log.WriteLine();
#endif
break;
case BlockType.ATime:
#if DEBUG
Log.Write("ATime:");
#endif
ReadDateTimeVector(
dataVector,
numFiles,
delegate(int i, DateTime? time)
{
db._files[i].ATime = time;
#if DEBUG
Log.Write(" " + (time.HasValue ? time.Value.ToString() : "n/a"));
#endif
}
);
#if DEBUG
Log.WriteLine();
#endif
break;
case BlockType.MTime:
#if DEBUG
Log.Write("MTime:");
#endif
ReadDateTimeVector(
dataVector,
numFiles,
delegate(int i, DateTime? time)
{
db._files[i].MTime = time;
#if DEBUG
Log.Write(" " + (time.HasValue ? time.Value.ToString() : "n/a"));
#endif
}
);
#if DEBUG
Log.WriteLine();
#endif
break;
case BlockType.Dummy:
#if DEBUG
Log.Write("Dummy: " + size);
#endif
for (long j = 0; j < size; j++)
{
if (ReadByte() != 0)
{
throw new InvalidOperationException();
}
}
break;
default:
SkipData(size);
break;
}
var checkRecordsSize = (db._majorVersion > 0 || db._minorVersion > 2);
if (checkRecordsSize && _currentReader.Offset - oldPos != size)
{
throw new InvalidOperationException();
}
}
var emptyFileIndex = 0;
var sizeIndex = 0;
for (var i = 0; i < numFiles; i++)
{
var file = db._files[i];
file.HasStream = !emptyStreamVector[i];
if (file.HasStream)
{
file.IsDir = false;
file.IsAnti = false;
file.Size = unpackSizes[sizeIndex];
file.Crc = digests[sizeIndex];
sizeIndex++;
}
else
{
file.IsDir = !emptyFileVector[emptyFileIndex];
file.IsAnti = antiFileVector[emptyFileIndex];
emptyFileIndex++;
file.Size = 0;
file.Crc = null;
}
}
}
finally
{
#if DEBUG
Log.PopIndent();
#endif
}
}
}

View File

@@ -1,5 +1,7 @@
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.IO;
namespace SharpCompress.Common.SevenZip;
@@ -58,6 +60,28 @@ internal class SevenZipFilePart : FilePart
return new ReadOnlySubStream(folderStream, Header.Size, leaveOpen: false);
}
internal override async ValueTask<Stream?> GetCompressedStreamAsync(CancellationToken cancellationToken = default)
{
if (!Header.HasStream)
{
return Stream.Null;
}
var folderStream = await _database.GetFolderStreamAsync(_stream, Folder!, _database.PasswordProvider, cancellationToken);
var firstFileIndex = _database._folderStartFileIndex[_database._folders.IndexOf(Folder!)];
var skipCount = Index - firstFileIndex;
long skipSize = 0;
for (var i = 0; i < skipCount; i++)
{
skipSize += _database._files[firstFileIndex + i].Size;
}
if (skipSize > 0)
{
await folderStream.SkipAsync(skipSize, cancellationToken);
}
return new ReadOnlySubStream(folderStream, Header.Size, leaveOpen: false);
}
public CompressionType CompressionType
{
get

View File

@@ -23,24 +23,8 @@ internal sealed partial class StreamingZipHeaderFactory : ZipHeaderFactory
internal IEnumerable<ZipHeader> ReadStreamHeader(Stream stream)
{
if (stream is not SharpCompressStream) //ensure the stream is already a SharpCompressStream. So the buffer/size will already be set
{
//the original code wrapped this with RewindableStream. Wrap with SharpCompressStream as we can get the buffer size
if (stream is SourceStream src)
{
stream = new SharpCompressStream(
stream,
src.ReaderOptions.LeaveStreamOpen,
bufferSize: src.ReaderOptions.BufferSize
);
}
else
{
throw new ArgumentException("Stream must be a SharpCompressStream", nameof(stream));
}
}
var rewindableStream = (SharpCompressStream)stream;
//the original code wrapped this with RewindableStream. Wrap with SharpCompressStream as we can get the buffer size
var rewindableStream = stream;
while (true)
{
var reader = new BinaryReader(rewindableStream);

View File

@@ -0,0 +1,74 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common.SevenZip;
using SharpCompress.Compressors.BZip2;
using SharpCompress.Compressors.Deflate;
using SharpCompress.Compressors.Filters;
using SharpCompress.Compressors.LZMA.Utilites;
using SharpCompress.Compressors.PPMd;
using SharpCompress.Compressors.ZStandard;
namespace SharpCompress.Compressors.LZMA;
internal static partial class DecoderRegistry
{
internal static async ValueTask<Stream> CreateDecoderStreamAsync(
CMethodId id,
Stream[] inStreams,
byte[] info,
IPasswordProvider pass,
long limit,
CancellationToken cancellationToken
)
{
switch (id._id)
{
case K_COPY:
if (info != null)
{
throw new NotSupportedException();
}
return inStreams.Single();
case K_DELTA:
return new DeltaFilter(false, inStreams.Single(), info);
case K_LZMA:
case K_LZMA2:
return await LzmaStream
.CreateAsync(info, inStreams.Single(), -1, limit, null, info.Length < 5, false)
.ConfigureAwait(false);
case CMethodId.K_AES_ID:
return new AesDecoderStream(inStreams.Single(), info, pass, limit);
case K_BCJ:
return new BCJFilter(false, inStreams.Single());
case K_BCJ2:
return new Bcj2DecoderStream(inStreams, info, limit);
case K_PPC:
return new BCJFilterPPC(false, inStreams.Single());
case K_IA64:
return new BCJFilterIA64(false, inStreams.Single());
case K_ARM:
return new BCJFilterARM(false, inStreams.Single());
case K_ARMT:
return new BCJFilterARMT(false, inStreams.Single());
case K_SPARC:
return new BCJFilterSPARC(false, inStreams.Single());
case K_ARM64:
return new BCJFilterARM64(false, inStreams.Single());
case K_RISCV:
return new BCJFilterRISCV(false, inStreams.Single());
case K_B_ZIP2:
return await BZip2Stream.CreateAsync(inStreams.Single(), CompressionMode.Decompress, true, cancellationToken: cancellationToken);
case K_PPMD:
return new PpmdStream(new PpmdProperties(info), inStreams.Single(), false);
case K_DEFLATE:
return new DeflateStream(inStreams.Single(), CompressionMode.Decompress);
case K_ZSTD:
return new DecompressionStream(inStreams.Single());
default:
throw new NotSupportedException();
}
}
}

View File

@@ -1,5 +1,7 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.SevenZip;
using SharpCompress.Compressors.LZMA.Utilites;
@@ -180,6 +182,99 @@ internal static class DecoderStreamHelper
);
}
private static async ValueTask<Stream> CreateDecoderStreamAsync(
Stream[] packStreams,
long[] packSizes,
Stream[] outStreams,
CFolder folderInfo,
int coderIndex,
IPasswordProvider pass,
CancellationToken cancellationToken
)
{
var coderInfo = folderInfo._coders[coderIndex];
if (coderInfo._numOutStreams != 1)
{
throw new NotSupportedException("Multiple output streams are not supported.");
}
var inStreamId = 0;
for (var i = 0; i < coderIndex; i++)
{
inStreamId += folderInfo._coders[i]._numInStreams;
}
var outStreamId = 0;
for (var i = 0; i < coderIndex; i++)
{
outStreamId += folderInfo._coders[i]._numOutStreams;
}
var inStreams = new Stream[coderInfo._numInStreams];
for (var i = 0; i < inStreams.Length; i++, inStreamId++)
{
var bindPairIndex = folderInfo.FindBindPairForInStream(inStreamId);
if (bindPairIndex >= 0)
{
var pairedOutIndex = folderInfo._bindPairs[bindPairIndex]._outIndex;
if (outStreams[pairedOutIndex] != null)
{
throw new NotSupportedException(
"Overlapping stream bindings are not supported."
);
}
var otherCoderIndex = FindCoderIndexForOutStreamIndex(folderInfo, pairedOutIndex);
inStreams[i] = await CreateDecoderStreamAsync(
packStreams,
packSizes,
outStreams,
folderInfo,
otherCoderIndex,
pass,
cancellationToken
)
.ConfigureAwait(false);
//inStreamSizes[i] = folderInfo.UnpackSizes[pairedOutIndex];
if (outStreams[pairedOutIndex] != null)
{
throw new NotSupportedException(
"Overlapping stream bindings are not supported."
);
}
outStreams[pairedOutIndex] = inStreams[i];
}
else
{
var index = folderInfo.FindPackStreamArrayIndex(inStreamId);
if (index < 0)
{
throw new NotSupportedException("Could not find input stream binding.");
}
inStreams[i] = packStreams[index];
//inStreamSizes[i] = packSizes[index];
}
}
var unpackSize = folderInfo._unpackSizes[outStreamId];
return await DecoderRegistry.CreateDecoderStreamAsync(
coderInfo._methodId,
inStreams,
coderInfo._props,
pass,
unpackSize,
cancellationToken
)
.ConfigureAwait(false);
}
internal static Stream CreateDecoderStream(
Stream inStream,
long startPos,
@@ -216,4 +311,44 @@ internal static class DecoderStreamHelper
pass
);
}
internal static async ValueTask<Stream> CreateDecoderStreamAsync(
Stream inStream,
long startPos,
long[] packSizes,
CFolder folderInfo,
IPasswordProvider pass,
CancellationToken cancellationToken
)
{
if (!folderInfo.CheckStructure())
{
throw new NotSupportedException("Unsupported stream binding structure.");
}
var inStreams = new Stream[folderInfo._packStreams.Count];
for (var j = 0; j < folderInfo._packStreams.Count; j++)
{
inStreams[j] = new BufferedSubStream(inStream, startPos, packSizes[j]);
startPos += packSizes[j];
}
var outStreams = new Stream[folderInfo._unpackSizes.Count];
FindPrimaryOutStreamIndex(
folderInfo,
out var primaryCoderIndex,
out var primaryOutStreamIndex
);
return await CreateDecoderStreamAsync(
inStreams,
packSizes,
outStreams,
folderInfo,
primaryCoderIndex,
pass,
cancellationToken
)
.ConfigureAwait(false);
}
}

View File

@@ -130,7 +130,7 @@ public partial class LzmaStream
_decoder.SetDecoderProperties(Properties);
}
_rangeDecoder.Init(_inputStream);
await _rangeDecoder.InitAsync(_inputStream, cancellationToken);
}
else if (control > 0x02)
{

View File

@@ -11,7 +11,7 @@ using SharpCompress.Compressors.ZStandard;
namespace SharpCompress.Compressors.LZMA;
internal static class DecoderRegistry
internal static partial class DecoderRegistry
{
private const uint K_COPY = 0x0;
private const uint K_DELTA = 0x3;

View File

@@ -18,10 +18,12 @@ public abstract partial class AbstractReader<TEntry, TVolume> : IReader, IAsyncR
private IEnumerator<TEntry>? _entriesForCurrentReadStream;
private IAsyncEnumerator<TEntry>? _entriesForCurrentReadStreamAsync;
private bool _wroteCurrentEntry;
private readonly bool _disposeVolume;
internal AbstractReader(ReaderOptions options, ArchiveType archiveType)
internal AbstractReader(ReaderOptions options, ArchiveType archiveType, bool disposeVolume = true)
{
ArchiveType = archiveType;
_disposeVolume = disposeVolume;
Options = options;
}
@@ -56,7 +58,10 @@ public abstract partial class AbstractReader<TEntry, TVolume> : IReader, IAsyncR
public virtual void Dispose()
{
_entriesForCurrentReadStream?.Dispose();
Volume?.Dispose();
if (_disposeVolume)
{
Volume?.Dispose();
}
}
#endregion

View File

@@ -44,10 +44,8 @@ public class ArchiveTests : ReaderTests
foreach (var path in testArchives)
{
using (
var stream = SharpCompressStream.Create(
File.OpenRead(path),
leaveOpen: true,
throwOnDispose: true
var stream = new NonDisposingStream(
File.OpenRead(path)
)
)
{