mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-23 23:45:14 +00:00
multi-file rars done manually
This commit is contained in:
@@ -102,7 +102,9 @@ public class RarArchiveEntry : RarEntry, IArchiveEntry
|
||||
stream = new RarStream(
|
||||
archive.UnpackV1.Value,
|
||||
FileHeader,
|
||||
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>())
|
||||
await MultiVolumeReadOnlyAsyncStream.Create(
|
||||
Parts.ToAsyncEnumerable().CastAsync<RarFilePart>()
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
@@ -110,7 +112,9 @@ public class RarArchiveEntry : RarEntry, IArchiveEntry
|
||||
stream = new RarStream(
|
||||
archive.UnpackV2017.Value,
|
||||
FileHeader,
|
||||
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>())
|
||||
await MultiVolumeReadOnlyAsyncStream.Create(
|
||||
Parts.ToAsyncEnumerable().CastAsync<RarFilePart>()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Rar;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Rar;
|
||||
|
||||
internal sealed class MultiVolumeReadOnlyAsyncStream : MultiVolumeReadOnlyStreamBase, IStreamStack
|
||||
{
|
||||
#if DEBUG_STREAMS
|
||||
long IStreamStack.InstanceId { get; set; }
|
||||
#endif
|
||||
int IStreamStack.DefaultBufferSize { get; set; }
|
||||
|
||||
Stream IStreamStack.BaseStream() => currentStream.NotNull();
|
||||
|
||||
int IStreamStack.BufferSize
|
||||
{
|
||||
get => 0;
|
||||
set { }
|
||||
}
|
||||
|
||||
int IStreamStack.BufferPosition
|
||||
{
|
||||
get => 0;
|
||||
set { }
|
||||
}
|
||||
|
||||
void IStreamStack.SetPosition(long position) { }
|
||||
|
||||
private long currentPosition;
|
||||
private long maxPosition;
|
||||
|
||||
private IAsyncEnumerator<RarFilePart> filePartEnumerator;
|
||||
private Stream? currentStream;
|
||||
|
||||
private MultiVolumeReadOnlyAsyncStream(IAsyncEnumerable<RarFilePart> parts)
|
||||
{
|
||||
filePartEnumerator = parts.GetAsyncEnumerator();
|
||||
}
|
||||
|
||||
internal static async ValueTask<MultiVolumeReadOnlyAsyncStream> Create(
|
||||
IAsyncEnumerable<RarFilePart> parts
|
||||
)
|
||||
{
|
||||
var stream = new MultiVolumeReadOnlyAsyncStream(parts);
|
||||
await stream.filePartEnumerator.MoveNextAsync();
|
||||
stream.InitializeNextFilePart();
|
||||
return stream;
|
||||
}
|
||||
|
||||
#if NET8_0_OR_GREATER
|
||||
public override async ValueTask DisposeAsync()
|
||||
{
|
||||
await base.DisposeAsync();
|
||||
if (filePartEnumerator != null)
|
||||
{
|
||||
await filePartEnumerator.DisposeAsync();
|
||||
}
|
||||
currentStream = null;
|
||||
}
|
||||
#else
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
filePartEnumerator.DisposeAsync().AsTask().GetAwaiter().GetResult();
|
||||
|
||||
currentStream = null;
|
||||
}
|
||||
#endif
|
||||
|
||||
private void InitializeNextFilePart()
|
||||
{
|
||||
maxPosition = filePartEnumerator.Current.FileHeader.CompressedSize;
|
||||
currentPosition = 0;
|
||||
currentStream = filePartEnumerator.Current.GetCompressedStream();
|
||||
|
||||
CurrentCrc = filePartEnumerator.Current.FileHeader.FileCrc;
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count) =>
|
||||
throw new NotSupportedException(
|
||||
"Synchronous read is not supported in MultiVolumeReadOnlyAsyncStream."
|
||||
);
|
||||
|
||||
public override async System.Threading.Tasks.Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
System.Threading.CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var totalRead = 0;
|
||||
var currentOffset = offset;
|
||||
var currentCount = count;
|
||||
while (currentCount > 0)
|
||||
{
|
||||
var readSize = currentCount;
|
||||
if (currentCount > maxPosition - currentPosition)
|
||||
{
|
||||
readSize = (int)(maxPosition - currentPosition);
|
||||
}
|
||||
|
||||
var read = await currentStream
|
||||
.NotNull()
|
||||
.ReadAsync(buffer, currentOffset, readSize, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (read < 0)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
currentPosition += read;
|
||||
currentOffset += read;
|
||||
currentCount -= read;
|
||||
totalRead += read;
|
||||
if (
|
||||
((maxPosition - currentPosition) == 0)
|
||||
&& filePartEnumerator.Current.FileHeader.IsSplitAfter
|
||||
)
|
||||
{
|
||||
if (filePartEnumerator.Current.FileHeader.R4Salt != null)
|
||||
{
|
||||
throw new InvalidFormatException(
|
||||
"Sharpcompress currently does not support multi-volume decryption."
|
||||
);
|
||||
}
|
||||
|
||||
var fileName = filePartEnumerator.Current.FileHeader.FileName;
|
||||
if (!await filePartEnumerator.MoveNextAsync())
|
||||
{
|
||||
throw new InvalidFormatException(
|
||||
"Multi-part rar file is incomplete. Entry expects a new volume: "
|
||||
+ fileName
|
||||
);
|
||||
}
|
||||
|
||||
InitializeNextFilePart();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
|
||||
public override async System.Threading.Tasks.ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
System.Threading.CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var totalRead = 0;
|
||||
var currentOffset = 0;
|
||||
var currentCount = buffer.Length;
|
||||
while (currentCount > 0)
|
||||
{
|
||||
var readSize = currentCount;
|
||||
if (currentCount > maxPosition - currentPosition)
|
||||
{
|
||||
readSize = (int)(maxPosition - currentPosition);
|
||||
}
|
||||
|
||||
var read = await currentStream
|
||||
.NotNull()
|
||||
.ReadAsync(buffer.Slice(currentOffset, readSize), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (read < 0)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
currentPosition += read;
|
||||
currentOffset += read;
|
||||
currentCount -= read;
|
||||
totalRead += read;
|
||||
if (
|
||||
((maxPosition - currentPosition) == 0)
|
||||
&& filePartEnumerator.Current.FileHeader.IsSplitAfter
|
||||
)
|
||||
{
|
||||
if (filePartEnumerator.Current.FileHeader.R4Salt != null)
|
||||
{
|
||||
throw new InvalidFormatException(
|
||||
"Sharpcompress currently does not support multi-volume decryption."
|
||||
);
|
||||
}
|
||||
var fileName = filePartEnumerator.Current.FileHeader.FileName;
|
||||
if (!await filePartEnumerator.MoveNextAsync())
|
||||
{
|
||||
throw new InvalidFormatException(
|
||||
"Multi-part rar file is incomplete. Entry expects a new volume: "
|
||||
+ fileName
|
||||
);
|
||||
}
|
||||
InitializeNextFilePart();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return totalRead;
|
||||
}
|
||||
#endif
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -9,20 +7,21 @@ using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Rar;
|
||||
|
||||
internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack
|
||||
internal sealed class MultiVolumeReadOnlyStream : MultiVolumeReadOnlyStreamBase, IStreamStack
|
||||
{
|
||||
#if DEBUG_STREAMS
|
||||
long IStreamStack.InstanceId { get; set; }
|
||||
#endif
|
||||
int IStreamStack.DefaultBufferSize { get; set; }
|
||||
|
||||
Stream IStreamStack.BaseStream() => currentStream;
|
||||
Stream IStreamStack.BaseStream() => currentStream.NotNull();
|
||||
|
||||
int IStreamStack.BufferSize
|
||||
{
|
||||
get => 0;
|
||||
set { }
|
||||
}
|
||||
|
||||
int IStreamStack.BufferPosition
|
||||
{
|
||||
get => 0;
|
||||
@@ -35,7 +34,7 @@ internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack
|
||||
private long maxPosition;
|
||||
|
||||
private IEnumerator<RarFilePart> filePartEnumerator;
|
||||
private Stream currentStream;
|
||||
private Stream? currentStream;
|
||||
|
||||
internal MultiVolumeReadOnlyStream(IEnumerable<RarFilePart> parts)
|
||||
{
|
||||
@@ -56,11 +55,8 @@ internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack
|
||||
this.DebugDispose(typeof(MultiVolumeReadOnlyStream));
|
||||
#endif
|
||||
|
||||
if (filePartEnumerator != null)
|
||||
{
|
||||
filePartEnumerator.Dispose();
|
||||
filePartEnumerator = null;
|
||||
}
|
||||
filePartEnumerator.Dispose();
|
||||
|
||||
currentStream = null;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +83,7 @@ internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack
|
||||
readSize = (int)(maxPosition - currentPosition);
|
||||
}
|
||||
|
||||
var read = currentStream.Read(buffer, currentOffset, readSize);
|
||||
var read = currentStream.NotNull().Read(buffer, currentOffset, readSize);
|
||||
if (read < 0)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
@@ -108,6 +104,7 @@ internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack
|
||||
"Sharpcompress currently does not support multi-volume decryption."
|
||||
);
|
||||
}
|
||||
|
||||
var fileName = filePartEnumerator.Current.FileHeader.FileName;
|
||||
if (!filePartEnumerator.MoveNext())
|
||||
{
|
||||
@@ -116,6 +113,7 @@ internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack
|
||||
+ fileName
|
||||
);
|
||||
}
|
||||
|
||||
InitializeNextFilePart();
|
||||
}
|
||||
else
|
||||
@@ -123,6 +121,7 @@ internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
@@ -145,6 +144,7 @@ internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack
|
||||
}
|
||||
|
||||
var read = await currentStream
|
||||
.NotNull()
|
||||
.ReadAsync(buffer, currentOffset, readSize, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (read < 0)
|
||||
@@ -167,6 +167,7 @@ internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack
|
||||
"Sharpcompress currently does not support multi-volume decryption."
|
||||
);
|
||||
}
|
||||
|
||||
var fileName = filePartEnumerator.Current.FileHeader.FileName;
|
||||
if (!filePartEnumerator.MoveNext())
|
||||
{
|
||||
@@ -175,6 +176,7 @@ internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack
|
||||
+ fileName
|
||||
);
|
||||
}
|
||||
|
||||
InitializeNextFilePart();
|
||||
}
|
||||
else
|
||||
@@ -182,6 +184,7 @@ internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
@@ -203,6 +206,7 @@ internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack
|
||||
}
|
||||
|
||||
var read = await currentStream
|
||||
.NotNull()
|
||||
.ReadAsync(buffer.Slice(currentOffset, readSize), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (read < 0)
|
||||
@@ -250,8 +254,6 @@ internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public byte[] CurrentCrc { get; private set; }
|
||||
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Rar;
|
||||
|
||||
internal abstract class MultiVolumeReadOnlyStreamBase : Stream
|
||||
{
|
||||
public byte[]? CurrentCrc { get; protected set; }
|
||||
}
|
||||
@@ -30,7 +30,7 @@ internal class RarBLAKE2spStream : RarStream, IStreamStack
|
||||
|
||||
void IStreamStack.SetPosition(long position) { }
|
||||
|
||||
private readonly MultiVolumeReadOnlyStream readStream;
|
||||
private readonly MultiVolumeReadOnlyStreamBase readStream;
|
||||
private readonly bool disableCRCCheck;
|
||||
|
||||
const uint BLAKE2S_NUM_ROUNDS = 10;
|
||||
@@ -108,7 +108,7 @@ internal class RarBLAKE2spStream : RarStream, IStreamStack
|
||||
private RarBLAKE2spStream(
|
||||
IRarUnpack unpack,
|
||||
FileHeader fileHeader,
|
||||
MultiVolumeReadOnlyStream readStream
|
||||
MultiVolumeReadOnlyStreamBase readStream
|
||||
)
|
||||
: base(unpack, fileHeader, readStream)
|
||||
{
|
||||
@@ -137,7 +137,7 @@ internal class RarBLAKE2spStream : RarStream, IStreamStack
|
||||
public static async Task<RarBLAKE2spStream> CreateAsync(
|
||||
IRarUnpack unpack,
|
||||
FileHeader fileHeader,
|
||||
MultiVolumeReadOnlyStream readStream,
|
||||
MultiVolumeReadOnlyAsyncStream readStream,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
|
||||
@@ -29,14 +29,14 @@ internal class RarCrcStream : RarStream, IStreamStack
|
||||
|
||||
void IStreamStack.SetPosition(long position) { }
|
||||
|
||||
private readonly MultiVolumeReadOnlyStream readStream;
|
||||
private readonly MultiVolumeReadOnlyStreamBase readStream;
|
||||
private uint currentCrc;
|
||||
private readonly bool disableCRC;
|
||||
|
||||
private RarCrcStream(
|
||||
IRarUnpack unpack,
|
||||
FileHeader fileHeader,
|
||||
MultiVolumeReadOnlyStream readStream
|
||||
MultiVolumeReadOnlyStreamBase readStream
|
||||
)
|
||||
: base(unpack, fileHeader, readStream)
|
||||
{
|
||||
@@ -62,7 +62,7 @@ internal class RarCrcStream : RarStream, IStreamStack
|
||||
public static async Task<RarCrcStream> CreateAsync(
|
||||
IRarUnpack unpack,
|
||||
FileHeader fileHeader,
|
||||
MultiVolumeReadOnlyStream readStream,
|
||||
MultiVolumeReadOnlyStreamBase readStream,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
@@ -92,7 +92,7 @@ internal class RarCrcStream : RarStream, IStreamStack
|
||||
}
|
||||
else if (
|
||||
!disableCRC
|
||||
&& GetCrc() != BitConverter.ToUInt32(readStream.CurrentCrc, 0)
|
||||
&& GetCrc() != BitConverter.ToUInt32(readStream.NotNull().CurrentCrc.NotNull(), 0)
|
||||
&& count != 0
|
||||
)
|
||||
{
|
||||
@@ -118,7 +118,7 @@ internal class RarCrcStream : RarStream, IStreamStack
|
||||
}
|
||||
else if (
|
||||
!disableCRC
|
||||
&& GetCrc() != BitConverter.ToUInt32(readStream.CurrentCrc, 0)
|
||||
&& GetCrc() != BitConverter.ToUInt32(readStream.NotNull().CurrentCrc.NotNull(), 0)
|
||||
&& count != 0
|
||||
)
|
||||
{
|
||||
@@ -143,7 +143,7 @@ internal class RarCrcStream : RarStream, IStreamStack
|
||||
}
|
||||
else if (
|
||||
!disableCRC
|
||||
&& GetCrc() != BitConverter.ToUInt32(readStream.CurrentCrc, 0)
|
||||
&& GetCrc() != BitConverter.ToUInt32(readStream.NotNull().CurrentCrc.NotNull(), 0)
|
||||
&& buffer.Length != 0
|
||||
)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,8 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Rar;
|
||||
|
||||
@@ -47,6 +49,13 @@ internal class MultiVolumeRarReader : RarReader
|
||||
return enumerator;
|
||||
}
|
||||
|
||||
protected override IAsyncEnumerable<FilePart> CreateFilePartEnumerableForCurrentEntryAsync()
|
||||
{
|
||||
var enumerator = new MultiVolumeStreamAsyncEnumerator(this, streams, tempStream);
|
||||
tempStream = null;
|
||||
return enumerator;
|
||||
}
|
||||
|
||||
private class MultiVolumeStreamEnumerator : IEnumerable<FilePart>, IEnumerator<FilePart>
|
||||
{
|
||||
private readonly MultiVolumeRarReader reader;
|
||||
@@ -110,4 +119,66 @@ internal class MultiVolumeRarReader : RarReader
|
||||
|
||||
public void Reset() { }
|
||||
}
|
||||
|
||||
private class MultiVolumeStreamAsyncEnumerator
|
||||
: IAsyncEnumerable<FilePart>,
|
||||
IAsyncEnumerator<FilePart>
|
||||
{
|
||||
private readonly MultiVolumeRarReader reader;
|
||||
private readonly IEnumerator<Stream> nextReadableStreams;
|
||||
private Stream tempStream;
|
||||
private bool isFirst = true;
|
||||
|
||||
internal MultiVolumeStreamAsyncEnumerator(
|
||||
MultiVolumeRarReader r,
|
||||
IEnumerator<Stream> nextReadableStreams,
|
||||
Stream tempStream
|
||||
)
|
||||
{
|
||||
reader = r;
|
||||
this.nextReadableStreams = nextReadableStreams;
|
||||
this.tempStream = tempStream;
|
||||
}
|
||||
|
||||
public FilePart Current { get; private set; }
|
||||
|
||||
public async ValueTask<bool> MoveNextAsync()
|
||||
{
|
||||
if (isFirst)
|
||||
{
|
||||
Current = reader.Entry.Parts.First();
|
||||
isFirst = false; //first stream already to go
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!reader.Entry.IsSplitAfter)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (tempStream != null)
|
||||
{
|
||||
await reader.LoadStreamForReadingAsync(tempStream);
|
||||
tempStream = null;
|
||||
}
|
||||
else if (!nextReadableStreams.MoveNext())
|
||||
{
|
||||
throw new MultiVolumeExtractionException(
|
||||
"No stream provided when requested by MultiVolumeRarReader"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
await reader.LoadStreamForReadingAsync(nextReadableStreams.Current);
|
||||
}
|
||||
|
||||
Current = reader.Entry.Parts.First();
|
||||
return true;
|
||||
}
|
||||
|
||||
public IAsyncEnumerator<FilePart> GetAsyncEnumerator(
|
||||
CancellationToken cancellationToken = new()
|
||||
) => this;
|
||||
|
||||
public ValueTask DisposeAsync() => new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,9 @@ public abstract partial class RarReader : AbstractReader<RarReaderEntry, RarVolu
|
||||
protected virtual IEnumerable<FilePart> CreateFilePartEnumerableForCurrentEntry() =>
|
||||
Entry.Parts;
|
||||
|
||||
protected virtual IAsyncEnumerable<FilePart> CreateFilePartEnumerableForCurrentEntryAsync() =>
|
||||
Entry.Parts.ToAsyncEnumerable();
|
||||
|
||||
protected override EntryStream GetEntryStream()
|
||||
{
|
||||
if (Entry.IsRedir)
|
||||
@@ -134,8 +137,8 @@ public abstract partial class RarReader : AbstractReader<RarReaderEntry, RarVolu
|
||||
throw new InvalidOperationException("no stream for redirect entry");
|
||||
}
|
||||
|
||||
var stream = new MultiVolumeReadOnlyStream(
|
||||
CreateFilePartEnumerableForCurrentEntry().Cast<RarFilePart>()
|
||||
var stream = await MultiVolumeReadOnlyAsyncStream.Create(
|
||||
CreateFilePartEnumerableForCurrentEntryAsync().CastAsync<RarFilePart>()
|
||||
);
|
||||
if (Entry.IsRarV3)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user