mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-22 15:04:43 +00:00
Added ARJ's compressmion method4 (compressed fastest).
Refactored TestBase to support archives with mixed compression algorithms.Merge branch 'adamhathcock:master' into master
This commit is contained in:
@@ -20,7 +20,7 @@ public static class ArchiveFactory
|
||||
public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
readerOptions ??= new ReaderOptions();
|
||||
stream = new SharpCompressStream(stream, bufferSize: readerOptions.BufferSize);
|
||||
stream = SharpCompressStream.Create(stream, bufferSize: readerOptions.BufferSize);
|
||||
return FindFactory<IArchiveFactory>(stream).Open(stream, readerOptions);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,17 @@ namespace SharpCompress.Common.Arj
|
||||
|
||||
public override long CompressedSize => _filePart?.Header.CompressedSize ?? 0;
|
||||
|
||||
public override CompressionType CompressionType => CompressionType.None;
|
||||
public override CompressionType CompressionType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_filePart.Header.CompressionMethod == CompressionMethod.Stored)
|
||||
{
|
||||
return CompressionType.None;
|
||||
}
|
||||
return CompressionType.ArjLZ77;
|
||||
}
|
||||
}
|
||||
|
||||
public override long Size => _filePart?.Header.OriginalSize ?? 0;
|
||||
|
||||
@@ -39,7 +49,7 @@ namespace SharpCompress.Common.Arj
|
||||
|
||||
public override bool IsEncrypted => false;
|
||||
|
||||
public override bool IsDirectory => _filePart.Header.FileType == (int)FileType.Directory;
|
||||
public override bool IsDirectory => _filePart.Header.FileType == FileType.Directory;
|
||||
|
||||
public override bool IsSplitAfter => false;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Arj.Headers;
|
||||
using SharpCompress.Compressors.Arj;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Common.Arj
|
||||
@@ -37,6 +38,18 @@ namespace SharpCompress.Common.Arj
|
||||
Header.CompressedSize
|
||||
);
|
||||
break;
|
||||
case CompressionMethod.CompressedFastest:
|
||||
byte[] compressedData = new byte[Header.CompressedSize];
|
||||
_stream.Position = Header.DataStartPosition;
|
||||
_stream.Read(compressedData, 0, compressedData.Length);
|
||||
|
||||
byte[] decompressedData = LHDecoder.DecodeFastest(
|
||||
compressedData,
|
||||
(int)Header.OriginalSize // ARJ can only handle files up to 2GB, so casting to int should not be an issue.
|
||||
);
|
||||
|
||||
compressedStream = new MemoryStream(decompressedData);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException(
|
||||
"CompressionMethod: " + Header.CompressionMethod
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace SharpCompress.Common.Arj.Headers
|
||||
}
|
||||
|
||||
public ArjHeaderType ArjHeaderType { get; }
|
||||
public byte Flags { get; set; }
|
||||
public FileType FileType { get; set; }
|
||||
|
||||
public abstract ArjHeader? Read(Stream reader);
|
||||
|
||||
@@ -34,23 +36,31 @@ namespace SharpCompress.Common.Arj.Headers
|
||||
// check for magic bytes
|
||||
Span<byte> magic = stackalloc byte[2];
|
||||
if (stream.Read(magic) != 2)
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
var magicValue = (ushort)(magic[0] | magic[1] << 8);
|
||||
if (magicValue != ARJ_MAGIC)
|
||||
{
|
||||
throw new InvalidDataException("Not an ARJ file (wrong magic bytes)");
|
||||
}
|
||||
|
||||
// read header_size
|
||||
byte[] headerBytes = new byte[2];
|
||||
stream.Read(headerBytes, 0, 2);
|
||||
var headerSize = (ushort)(headerBytes[0] | headerBytes[1] << 8);
|
||||
if (headerSize < 1)
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
var body = new byte[headerSize];
|
||||
var read = stream.Read(body, 0, headerSize);
|
||||
if (read < headerSize)
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
byte[] crc = new byte[4];
|
||||
read = stream.Read(crc, 0, 4);
|
||||
@@ -111,5 +121,22 @@ namespace SharpCompress.Common.Arj.Headers
|
||||
extendedHeader.Add(header);
|
||||
}
|
||||
}
|
||||
|
||||
// Flag helpers
|
||||
public bool IsGabled => (Flags & 0x01) != 0;
|
||||
public bool IsAnsiPage => (Flags & 0x02) != 0;
|
||||
public bool IsVolume => (Flags & 0x04) != 0;
|
||||
public bool IsArjProtected => (Flags & 0x08) != 0;
|
||||
public bool IsPathSym => (Flags & 0x10) != 0;
|
||||
public bool IsBackup => (Flags & 0x20) != 0;
|
||||
public bool IsSecured => (Flags & 0x40) != 0;
|
||||
public bool IsAltName => (Flags & 0x80) != 0;
|
||||
|
||||
public static FileType FileTypeFromByte(byte value)
|
||||
{
|
||||
return Enum.IsDefined(typeof(FileType), value)
|
||||
? (FileType)value
|
||||
: Headers.FileType.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,7 @@ namespace SharpCompress.Common.Arj.Headers
|
||||
public byte ArchiverVersionNumber { get; set; }
|
||||
public byte MinVersionToExtract { get; set; }
|
||||
public HostOS HostOS { get; set; }
|
||||
public byte ArjFlags { get; set; }
|
||||
public CompressionMethod CompressionMethod { get; set; }
|
||||
public int FileType { get; set; }
|
||||
public DosDateTime DateTimeModified { get; set; } = new DosDateTime(0);
|
||||
public long CompressedSize { get; set; }
|
||||
public long OriginalSize { get; set; }
|
||||
@@ -36,11 +34,6 @@ namespace SharpCompress.Common.Arj.Headers
|
||||
|
||||
private const byte StdHdrSize = 30;
|
||||
private const byte R9HdrSize = 46;
|
||||
public bool IsGarbled => (ArjFlags & 0x01) != 0;
|
||||
public bool IsVolume => (ArjFlags & 0x04) != 0;
|
||||
public bool IsExtFile => (ArjFlags & 0x08) != 0;
|
||||
public bool IsPathSym => (ArjFlags & 0x10) != 0;
|
||||
public bool IsBackup => (ArjFlags & 0x20) != 0;
|
||||
|
||||
public ArjLocalHeader(ArchiveEncoding archiveEncoding)
|
||||
: base(ArjHeaderType.LocalHeader)
|
||||
@@ -92,12 +85,12 @@ namespace SharpCompress.Common.Arj.Headers
|
||||
}
|
||||
|
||||
byte headerSize = headerBytes[offset++];
|
||||
byte archiverVersionNumber = headerBytes[offset++];
|
||||
byte minVersionToExtract = headerBytes[offset++];
|
||||
ArchiverVersionNumber = headerBytes[offset++];
|
||||
MinVersionToExtract = headerBytes[offset++];
|
||||
HostOS hostOS = (HostOS)headerBytes[offset++];
|
||||
byte arjFlags = headerBytes[offset++];
|
||||
CompressionMethod compressionMethod = CompressionMethodFromByte(headerBytes[offset++]);
|
||||
FileType fileType = FileTypeFromByte(headerBytes[offset++]);
|
||||
Flags = headerBytes[offset++];
|
||||
CompressionMethod = CompressionMethodFromByte(headerBytes[offset++]);
|
||||
FileType = FileTypeFromByte(headerBytes[offset++]);
|
||||
|
||||
offset++; // Skip 1 byte
|
||||
|
||||
@@ -164,12 +157,5 @@ namespace SharpCompress.Common.Arj.Headers
|
||||
_ => CompressionMethod.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
public static FileType FileTypeFromByte(byte value)
|
||||
{
|
||||
return Enum.IsDefined(typeof(FileType), value)
|
||||
? (FileType)value
|
||||
: Headers.FileType.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,7 @@ namespace SharpCompress.Common.Arj.Headers
|
||||
public int ArchiverVersionNumber { get; private set; }
|
||||
public int MinVersionToExtract { get; private set; }
|
||||
public HostOS HostOs { get; private set; }
|
||||
public int Flags { get; private set; }
|
||||
public int SecurityVersion { get; private set; }
|
||||
public int FileType { get; private set; }
|
||||
public DosDateTime CreationDateTime { get; private set; } = new DosDateTime(0);
|
||||
public long CompressedSize { get; private set; }
|
||||
public long ArchiveSize { get; private set; }
|
||||
@@ -51,13 +49,13 @@ namespace SharpCompress.Common.Arj.Headers
|
||||
{
|
||||
var offset = 1;
|
||||
|
||||
int ReadByte()
|
||||
byte ReadByte()
|
||||
{
|
||||
if (offset >= headerBytes.Length)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
return headerBytes[offset++] & 0xFF;
|
||||
return (byte)(headerBytes[offset++] & 0xFF);
|
||||
}
|
||||
|
||||
int ReadInt16()
|
||||
@@ -116,7 +114,7 @@ namespace SharpCompress.Common.Arj.Headers
|
||||
|
||||
Flags = ReadByte();
|
||||
SecurityVersion = ReadByte();
|
||||
FileType = ReadByte();
|
||||
FileType = FileTypeFromByte(ReadByte());
|
||||
|
||||
offset++; // skip reserved
|
||||
|
||||
@@ -136,15 +134,5 @@ namespace SharpCompress.Common.Arj.Headers
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// Flag helpers
|
||||
public bool IsGabled => (Flags & 0x01) != 0;
|
||||
public bool IsAnsiPage => (Flags & 0x02) != 0;
|
||||
public bool IsVolume => (Flags & 0x04) != 0;
|
||||
public bool IsArjProtected => (Flags & 0x08) != 0;
|
||||
public bool IsPathSym => (Flags & 0x10) != 0;
|
||||
public bool IsBackup => (Flags & 0x20) != 0;
|
||||
public bool IsSecured => (Flags & 0x40) != 0;
|
||||
public bool IsAltName => (Flags & 0x80) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,4 +29,5 @@ public enum CompressionType
|
||||
Crushed,
|
||||
Distilled,
|
||||
ZStandard,
|
||||
ArjLZ77,
|
||||
}
|
||||
|
||||
@@ -24,10 +24,29 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Compressors.ADC;
|
||||
|
||||
/// <summary>
|
||||
/// Result of an ADC decompression operation
|
||||
/// </summary>
|
||||
public class AdcDecompressResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of bytes read from input
|
||||
/// </summary>
|
||||
public int BytesRead { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Decompressed output buffer
|
||||
/// </summary>
|
||||
public byte[]? Output { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides static methods for decompressing Apple Data Compression data
|
||||
/// </summary>
|
||||
@@ -78,6 +97,173 @@ public static class ADCBase
|
||||
public static int Decompress(byte[] input, out byte[]? output, int bufferSize = 262144) =>
|
||||
Decompress(new MemoryStream(input), out output, bufferSize);
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses a byte buffer asynchronously that's compressed with ADC
|
||||
/// </summary>
|
||||
/// <param name="input">Compressed buffer</param>
|
||||
/// <param name="bufferSize">Max size for decompressed data</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Result containing bytes read and decompressed data</returns>
|
||||
public static async Task<AdcDecompressResult> DecompressAsync(
|
||||
byte[] input,
|
||||
int bufferSize = 262144,
|
||||
CancellationToken cancellationToken = default
|
||||
) => await DecompressAsync(new MemoryStream(input), bufferSize, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses a stream asynchronously that's compressed with ADC
|
||||
/// </summary>
|
||||
/// <param name="input">Stream containing compressed data</param>
|
||||
/// <param name="bufferSize">Max size for decompressed data</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Result containing bytes read and decompressed data</returns>
|
||||
public static async Task<AdcDecompressResult> DecompressAsync(
|
||||
Stream input,
|
||||
int bufferSize = 262144,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var result = new AdcDecompressResult();
|
||||
|
||||
if (input is null || input.Length == 0)
|
||||
{
|
||||
result.BytesRead = 0;
|
||||
result.Output = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
var start = (int)input.Position;
|
||||
var position = (int)input.Position;
|
||||
int chunkSize;
|
||||
int offset;
|
||||
int chunkType;
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||
var outPosition = 0;
|
||||
var full = false;
|
||||
byte[] temp = ArrayPool<byte>.Shared.Rent(3);
|
||||
|
||||
try
|
||||
{
|
||||
while (position < input.Length)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var readByte = input.ReadByte();
|
||||
if (readByte == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
chunkType = GetChunkType((byte)readByte);
|
||||
|
||||
switch (chunkType)
|
||||
{
|
||||
case PLAIN:
|
||||
chunkSize = GetChunkSize((byte)readByte);
|
||||
if (outPosition + chunkSize > bufferSize)
|
||||
{
|
||||
full = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var readCount = await input.ReadAsync(
|
||||
buffer,
|
||||
outPosition,
|
||||
chunkSize,
|
||||
cancellationToken
|
||||
);
|
||||
outPosition += readCount;
|
||||
position += readCount + 1;
|
||||
break;
|
||||
case TWO_BYTE:
|
||||
chunkSize = GetChunkSize((byte)readByte);
|
||||
temp[0] = (byte)readByte;
|
||||
temp[1] = (byte)input.ReadByte();
|
||||
offset = GetOffset(temp.AsSpan(0, 2));
|
||||
if (outPosition + chunkSize > bufferSize)
|
||||
{
|
||||
full = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (offset == 0)
|
||||
{
|
||||
var lastByte = buffer[outPosition - 1];
|
||||
for (var i = 0; i < chunkSize; i++)
|
||||
{
|
||||
buffer[outPosition] = lastByte;
|
||||
outPosition++;
|
||||
}
|
||||
|
||||
position += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < chunkSize; i++)
|
||||
{
|
||||
buffer[outPosition] = buffer[outPosition - offset - 1];
|
||||
outPosition++;
|
||||
}
|
||||
|
||||
position += 2;
|
||||
}
|
||||
|
||||
break;
|
||||
case THREE_BYTE:
|
||||
chunkSize = GetChunkSize((byte)readByte);
|
||||
temp[0] = (byte)readByte;
|
||||
temp[1] = (byte)input.ReadByte();
|
||||
temp[2] = (byte)input.ReadByte();
|
||||
offset = GetOffset(temp.AsSpan(0, 3));
|
||||
if (outPosition + chunkSize > bufferSize)
|
||||
{
|
||||
full = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (offset == 0)
|
||||
{
|
||||
var lastByte = buffer[outPosition - 1];
|
||||
for (var i = 0; i < chunkSize; i++)
|
||||
{
|
||||
buffer[outPosition] = lastByte;
|
||||
outPosition++;
|
||||
}
|
||||
|
||||
position += 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < chunkSize; i++)
|
||||
{
|
||||
buffer[outPosition] = buffer[outPosition - offset - 1];
|
||||
outPosition++;
|
||||
}
|
||||
|
||||
position += 3;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (full)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var output = new byte[outPosition];
|
||||
Array.Copy(buffer, output, outPosition);
|
||||
result.BytesRead = position - start;
|
||||
result.Output = output;
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
ArrayPool<byte>.Shared.Return(temp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses a stream that's compressed with ADC
|
||||
/// </summary>
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.ADC;
|
||||
@@ -187,6 +189,76 @@ public sealed class ADCStream : Stream, IStreamStack
|
||||
return copied;
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (buffer is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
if (count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
}
|
||||
if (offset < buffer.GetLowerBound(0))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
}
|
||||
if ((offset + count) > buffer.GetLength(0))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
}
|
||||
|
||||
if (_outBuffer is null)
|
||||
{
|
||||
var result = await ADCBase.DecompressAsync(
|
||||
_stream,
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
_outBuffer = result.Output;
|
||||
_outPosition = 0;
|
||||
}
|
||||
|
||||
var inPosition = offset;
|
||||
var toCopy = count;
|
||||
var copied = 0;
|
||||
|
||||
while (_outPosition + toCopy >= _outBuffer.Length)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var piece = _outBuffer.Length - _outPosition;
|
||||
Array.Copy(_outBuffer, _outPosition, buffer, inPosition, piece);
|
||||
inPosition += piece;
|
||||
copied += piece;
|
||||
_position += piece;
|
||||
toCopy -= piece;
|
||||
var result = await ADCBase.DecompressAsync(
|
||||
_stream,
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
_outBuffer = result.Output;
|
||||
_outPosition = 0;
|
||||
if (result.BytesRead == 0 || _outBuffer is null || _outBuffer.Length == 0)
|
||||
{
|
||||
return copied;
|
||||
}
|
||||
}
|
||||
|
||||
Array.Copy(_outBuffer, _outPosition, buffer, inPosition, toCopy);
|
||||
_outPosition += toCopy;
|
||||
_position += toCopy;
|
||||
copied += toCopy;
|
||||
return copied;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
99
src/SharpCompress/Compressors/Arj/DecodeFastest.cs
Normal file
99
src/SharpCompress/Compressors/Arj/DecodeFastest.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace SharpCompress.Compressors.Arj
|
||||
{
|
||||
[CLSCompliant(true)]
|
||||
public class BitReader
|
||||
{
|
||||
private readonly byte[] data;
|
||||
private int bytePos = 0;
|
||||
private int bitPos = 0;
|
||||
|
||||
public BitReader(byte[] input)
|
||||
{
|
||||
data = input;
|
||||
}
|
||||
|
||||
public int ReadBits(int count)
|
||||
{
|
||||
int result = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (bytePos >= data.Length)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
int bit = (data[bytePos] >> (7 - bitPos)) & 1;
|
||||
result = (result << 1) | bit;
|
||||
|
||||
bitPos++;
|
||||
if (bitPos == 8)
|
||||
{
|
||||
bitPos = 0;
|
||||
bytePos++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
[CLSCompliant(true)]
|
||||
public static class LHDecoder
|
||||
{
|
||||
private const int THRESHOLD = 3;
|
||||
|
||||
private static int DecodeVal(BitReader r, int from, int to)
|
||||
{
|
||||
int add = 0;
|
||||
int bit = from;
|
||||
|
||||
while (bit < to && r.ReadBits(1) == 1)
|
||||
{
|
||||
add |= 1 << bit;
|
||||
bit++;
|
||||
}
|
||||
|
||||
int res = bit > 0 ? r.ReadBits(bit) : 0;
|
||||
return res + add;
|
||||
}
|
||||
|
||||
public static byte[] DecodeFastest(byte[] data, int originalSize)
|
||||
{
|
||||
var res = new List<byte>(originalSize);
|
||||
var r = new BitReader(data);
|
||||
|
||||
while (res.Count < originalSize)
|
||||
{
|
||||
int len = DecodeVal(r, 0, 7);
|
||||
if (len == 0)
|
||||
{
|
||||
byte nextChar = (byte)r.ReadBits(8);
|
||||
res.Add(nextChar);
|
||||
}
|
||||
else
|
||||
{
|
||||
int repCount = len + THRESHOLD - 1;
|
||||
int backPtr = DecodeVal(r, 9, 13);
|
||||
|
||||
if (backPtr >= res.Count)
|
||||
{
|
||||
throw new InvalidDataException("invalid back_ptr");
|
||||
}
|
||||
|
||||
int i = res.Count - 1 - backPtr;
|
||||
for (int j = 0; j < repCount; j++)
|
||||
{
|
||||
res.Add(res[i]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.BZip2;
|
||||
@@ -96,13 +98,37 @@ public sealed class BZip2Stream : Stream, IStreamStack
|
||||
|
||||
public override void SetLength(long value) => stream.SetLength(value);
|
||||
|
||||
#if !NETFRAMEWORK&& !NETSTANDARD2_0
|
||||
#if !NETFRAMEWORK && !NETSTANDARD2_0
|
||||
|
||||
public override int Read(Span<byte> buffer) => stream.Read(buffer);
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> buffer) => stream.Write(buffer);
|
||||
|
||||
public override async ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
) => await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
public override async ValueTask WriteAsync(
|
||||
ReadOnlyMemory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
) => await stream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
#endif
|
||||
|
||||
public override async Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
) => await stream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
public override async Task WriteAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
) => await stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
stream.Write(buffer, offset, count);
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
/*
|
||||
@@ -1127,6 +1129,28 @@ internal class CBZip2InputStream : Stream, IStreamStack
|
||||
return k;
|
||||
}
|
||||
|
||||
public override Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var c = -1;
|
||||
int k;
|
||||
for (k = 0; k < count; ++k)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
c = ReadByte();
|
||||
if (c == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
buffer[k + offset] = (byte)c;
|
||||
}
|
||||
return Task.FromResult(k);
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => 0;
|
||||
|
||||
public override void SetLength(long value) { }
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
/*
|
||||
@@ -2022,6 +2024,21 @@ internal sealed class CBZip2OutputStream : Stream, IStreamStack
|
||||
}
|
||||
}
|
||||
|
||||
public override Task WriteAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
for (var k = 0; k < count; ++k)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
WriteByte(buffer[k + offset]);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override bool CanRead => false;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Arj.Headers;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Readers.Arc;
|
||||
using SharpCompress.Readers.Arj;
|
||||
|
||||
namespace SharpCompress.Factories
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ using SharpCompress.Common.Arj.Headers;
|
||||
using SharpCompress.Common.Zip;
|
||||
using SharpCompress.Common.Zip.Headers;
|
||||
|
||||
namespace SharpCompress.Readers.Arc
|
||||
namespace SharpCompress.Readers.Arj
|
||||
{
|
||||
public class ArjReader : AbstractReader<ArjEntry, ArjVolume>
|
||||
{
|
||||
@@ -33,13 +33,14 @@ namespace SharpCompress.Readers.Arc
|
||||
|
||||
protected override IEnumerable<ArjEntry> GetEntries(Stream stream)
|
||||
{
|
||||
var headerReader = new ArjMainHeader(new ArchiveEncoding());
|
||||
var mainHeader = headerReader.Read(stream);
|
||||
ArchiveEncoding encoding = new ArchiveEncoding();
|
||||
var mainHeaderReader = new ArjMainHeader(encoding);
|
||||
var localHeaderReader = new ArjLocalHeader(encoding);
|
||||
var mainHeader = mainHeaderReader.Read(stream);
|
||||
|
||||
while (true)
|
||||
{
|
||||
var localReader = new ArjLocalHeader(new ArchiveEncoding());
|
||||
var localHeader = localReader.Read(stream);
|
||||
var localHeader = localHeaderReader.Read(stream);
|
||||
if (localHeader == null)
|
||||
break;
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ public class ZipWriter : AbstractWriter
|
||||
WriteDirectoryEntry(normalizedName, options);
|
||||
}
|
||||
|
||||
public override async Task WriteDirectoryAsync(
|
||||
public override Task WriteDirectoryAsync(
|
||||
string directoryName,
|
||||
DateTime? modificationTime,
|
||||
CancellationToken cancellationToken = default
|
||||
@@ -168,7 +168,7 @@ public class ZipWriter : AbstractWriter
|
||||
{
|
||||
// Synchronous implementation is sufficient for directory entries
|
||||
WriteDirectory(directoryName, modificationTime);
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void WriteDirectoryEntry(string directoryPath, ZipWriterEntryOptions options)
|
||||
|
||||
61
tests/SharpCompress.Test/AdcAsyncTest.cs
Normal file
61
tests/SharpCompress.Test/AdcAsyncTest.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Compressors.ADC;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test;
|
||||
|
||||
public class AdcAsyncTest : TestBase
|
||||
{
|
||||
[Fact]
|
||||
public async Task TestAdcStreamAsyncWholeChunk()
|
||||
{
|
||||
using var decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_decompressed.bin"));
|
||||
var decompressed = new byte[decFs.Length];
|
||||
decFs.Read(decompressed, 0, decompressed.Length);
|
||||
|
||||
using var cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin"));
|
||||
using var decStream = new ADCStream(cmpFs);
|
||||
var test = new byte[262144];
|
||||
|
||||
await decStream.ReadAsync(test, 0, test.Length);
|
||||
|
||||
Assert.Equal(decompressed, test);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TestAdcStreamAsync()
|
||||
{
|
||||
using var decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_decompressed.bin"));
|
||||
var decompressed = new byte[decFs.Length];
|
||||
decFs.Read(decompressed, 0, decompressed.Length);
|
||||
|
||||
using var cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin"));
|
||||
using var decStream = new ADCStream(cmpFs);
|
||||
using var decMs = new MemoryStream();
|
||||
var test = new byte[512];
|
||||
var count = 0;
|
||||
|
||||
do
|
||||
{
|
||||
count = await decStream.ReadAsync(test, 0, test.Length);
|
||||
decMs.Write(test, 0, count);
|
||||
} while (count > 0);
|
||||
|
||||
Assert.Equal(decompressed, decMs.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TestAdcStreamAsyncWithCancellation()
|
||||
{
|
||||
using var cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin"));
|
||||
using var decStream = new ADCStream(cmpFs);
|
||||
var test = new byte[512];
|
||||
using var cts = new System.Threading.CancellationTokenSource();
|
||||
|
||||
// Read should complete without cancellation
|
||||
var bytesRead = await decStream.ReadAsync(test, 0, test.Length, cts.Token);
|
||||
|
||||
Assert.True(bytesRead > 0);
|
||||
}
|
||||
}
|
||||
@@ -622,4 +622,36 @@ public class ArchiveTests : ReaderTests
|
||||
VerifyFiles();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArchiveFactory_Open_WithPreWrappedStream()
|
||||
{
|
||||
// Test that ArchiveFactory.Open works correctly with a stream that's already wrapped
|
||||
// This addresses the issue where ZIP files fail to open on Linux
|
||||
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.noEmptyDirs.zip");
|
||||
|
||||
// Open with a pre-wrapped stream
|
||||
using (var fileStream = File.OpenRead(testArchive))
|
||||
using (var wrappedStream = SharpCompressStream.Create(fileStream, bufferSize: 32768))
|
||||
using (var archive = ArchiveFactory.Open(wrappedStream))
|
||||
{
|
||||
Assert.Equal(ArchiveType.Zip, archive.Type);
|
||||
Assert.Equal(3, archive.Entries.Count());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArchiveFactory_Open_WithRawFileStream()
|
||||
{
|
||||
// Test that ArchiveFactory.Open works correctly with a raw FileStream
|
||||
// This is the common use case reported in the issue
|
||||
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.noEmptyDirs.zip");
|
||||
|
||||
using (var stream = File.OpenRead(testArchive))
|
||||
using (var archive = ArchiveFactory.Open(stream))
|
||||
{
|
||||
Assert.Equal(ArchiveType.Zip, archive.Type);
|
||||
Assert.Equal(3, archive.Entries.Count());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Readers.Arc;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.Arj
|
||||
@@ -21,26 +20,7 @@ namespace SharpCompress.Test.Arj
|
||||
|
||||
[Fact]
|
||||
public void Arj_Uncompressed_Read() => Read("Arj.store.arj", CompressionType.None);
|
||||
|
||||
private void ProcessArchive(string archiveName)
|
||||
{
|
||||
// Process a given archive by its name
|
||||
using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, archiveName)))
|
||||
using (IReader reader = ArjReader.Open(stream))
|
||||
{
|
||||
while (reader.MoveToNextEntry())
|
||||
{
|
||||
if (!reader.Entry.IsDirectory)
|
||||
{
|
||||
reader.WriteEntryToDirectory(
|
||||
SCRATCH_FILES_PATH,
|
||||
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VerifyFilesByExtension();
|
||||
}
|
||||
[Fact]
|
||||
public void Arj_Method4_Read() => Read("Arj.method4.arj");
|
||||
}
|
||||
}
|
||||
|
||||
231
tests/SharpCompress.Test/BZip2/BZip2StreamAsyncTests.cs
Normal file
231
tests/SharpCompress.Test/BZip2/BZip2StreamAsyncTests.cs
Normal file
@@ -0,0 +1,231 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Compressors.BZip2;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.BZip2;
|
||||
|
||||
public class BZip2StreamAsyncTests
|
||||
{
|
||||
private byte[] CreateTestData(int size)
|
||||
{
|
||||
var data = new byte[size];
|
||||
// Create compressible data with repetitive pattern
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
data[i] = (byte)('A' + (i % 26));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BZip2CompressDecompressAsyncTest()
|
||||
{
|
||||
var testData = CreateTestData(10000);
|
||||
byte[] compressed;
|
||||
|
||||
// Compress
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
using (
|
||||
var bzip2Stream = new BZip2Stream(
|
||||
memoryStream,
|
||||
SharpCompress.Compressors.CompressionMode.Compress,
|
||||
false
|
||||
)
|
||||
)
|
||||
{
|
||||
await bzip2Stream.WriteAsync(testData, 0, testData.Length);
|
||||
(bzip2Stream as BZip2Stream)?.Finish();
|
||||
}
|
||||
compressed = memoryStream.ToArray();
|
||||
}
|
||||
|
||||
// Verify compression occurred
|
||||
Assert.True(compressed.Length > 0);
|
||||
Assert.True(compressed.Length < testData.Length);
|
||||
|
||||
// Decompress
|
||||
byte[] decompressed;
|
||||
using (var memoryStream = new MemoryStream(compressed))
|
||||
{
|
||||
using (
|
||||
var bzip2Stream = new BZip2Stream(
|
||||
memoryStream,
|
||||
SharpCompress.Compressors.CompressionMode.Decompress,
|
||||
false
|
||||
)
|
||||
)
|
||||
{
|
||||
decompressed = new byte[testData.Length];
|
||||
var totalRead = 0;
|
||||
int bytesRead;
|
||||
while (
|
||||
(
|
||||
bytesRead = await bzip2Stream.ReadAsync(
|
||||
decompressed,
|
||||
totalRead,
|
||||
testData.Length - totalRead
|
||||
)
|
||||
) > 0
|
||||
)
|
||||
{
|
||||
totalRead += bytesRead;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify decompression
|
||||
Assert.Equal(testData, decompressed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BZip2ReadAsyncWithCancellationTest()
|
||||
{
|
||||
var testData = Encoding.ASCII.GetBytes(new string('A', 5000)); // Repetitive data compresses well
|
||||
byte[] compressed;
|
||||
|
||||
// Compress
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
using (
|
||||
var bzip2Stream = new BZip2Stream(
|
||||
memoryStream,
|
||||
SharpCompress.Compressors.CompressionMode.Compress,
|
||||
false
|
||||
)
|
||||
)
|
||||
{
|
||||
await bzip2Stream.WriteAsync(testData, 0, testData.Length);
|
||||
(bzip2Stream as BZip2Stream)?.Finish();
|
||||
}
|
||||
compressed = memoryStream.ToArray();
|
||||
}
|
||||
|
||||
// Decompress with cancellation support
|
||||
using (var memoryStream = new MemoryStream(compressed))
|
||||
{
|
||||
using (
|
||||
var bzip2Stream = new BZip2Stream(
|
||||
memoryStream,
|
||||
SharpCompress.Compressors.CompressionMode.Decompress,
|
||||
false
|
||||
)
|
||||
)
|
||||
{
|
||||
var buffer = new byte[1024];
|
||||
using var cts = new System.Threading.CancellationTokenSource();
|
||||
|
||||
// Read should complete without cancellation
|
||||
var bytesRead = await bzip2Stream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
|
||||
Assert.True(bytesRead > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BZip2MultipleAsyncWritesTest()
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
using (
|
||||
var bzip2Stream = new BZip2Stream(
|
||||
memoryStream,
|
||||
SharpCompress.Compressors.CompressionMode.Compress,
|
||||
false
|
||||
)
|
||||
)
|
||||
{
|
||||
var data1 = Encoding.ASCII.GetBytes("Hello ");
|
||||
var data2 = Encoding.ASCII.GetBytes("World");
|
||||
var data3 = Encoding.ASCII.GetBytes("!");
|
||||
|
||||
await bzip2Stream.WriteAsync(data1, 0, data1.Length);
|
||||
await bzip2Stream.WriteAsync(data2, 0, data2.Length);
|
||||
await bzip2Stream.WriteAsync(data3, 0, data3.Length);
|
||||
|
||||
(bzip2Stream as BZip2Stream)?.Finish();
|
||||
}
|
||||
|
||||
var compressed = memoryStream.ToArray();
|
||||
Assert.True(compressed.Length > 0);
|
||||
|
||||
// Decompress and verify
|
||||
using (var readStream = new MemoryStream(compressed))
|
||||
{
|
||||
using (
|
||||
var bzip2Stream = new BZip2Stream(
|
||||
readStream,
|
||||
SharpCompress.Compressors.CompressionMode.Decompress,
|
||||
false
|
||||
)
|
||||
)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
var buffer = new byte[256];
|
||||
int bytesRead;
|
||||
while ((bytesRead = await bzip2Stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
result.Append(Encoding.ASCII.GetString(buffer, 0, bytesRead));
|
||||
}
|
||||
|
||||
Assert.Equal("Hello World!", result.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BZip2LargeDataAsyncTest()
|
||||
{
|
||||
var largeData = CreateTestData(100000);
|
||||
|
||||
// Compress
|
||||
byte[] compressed;
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
using (
|
||||
var bzip2Stream = new BZip2Stream(
|
||||
memoryStream,
|
||||
SharpCompress.Compressors.CompressionMode.Compress,
|
||||
false
|
||||
)
|
||||
)
|
||||
{
|
||||
await bzip2Stream.WriteAsync(largeData, 0, largeData.Length);
|
||||
(bzip2Stream as BZip2Stream)?.Finish();
|
||||
}
|
||||
compressed = memoryStream.ToArray();
|
||||
}
|
||||
|
||||
// Decompress
|
||||
byte[] decompressed;
|
||||
using (var memoryStream = new MemoryStream(compressed))
|
||||
{
|
||||
using (
|
||||
var bzip2Stream = new BZip2Stream(
|
||||
memoryStream,
|
||||
SharpCompress.Compressors.CompressionMode.Decompress,
|
||||
false
|
||||
)
|
||||
)
|
||||
{
|
||||
decompressed = new byte[largeData.Length];
|
||||
var totalRead = 0;
|
||||
int bytesRead;
|
||||
var buffer = new byte[4096];
|
||||
while ((bytesRead = await bzip2Stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
Array.Copy(buffer, 0, decompressed, totalRead, bytesRead);
|
||||
totalRead += bytesRead;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify
|
||||
Assert.Equal(largeData, decompressed);
|
||||
}
|
||||
}
|
||||
@@ -13,29 +13,53 @@ namespace SharpCompress.Test;
|
||||
|
||||
public abstract class ReaderTests : TestBase
|
||||
{
|
||||
protected void Read(string testArchive, ReaderOptions? options = null)
|
||||
{
|
||||
ReadCore(testArchive, options, ReadImpl);
|
||||
}
|
||||
|
||||
protected void Read(
|
||||
string testArchive,
|
||||
CompressionType expectedCompression,
|
||||
ReaderOptions? options = null
|
||||
)
|
||||
{
|
||||
testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive);
|
||||
ReadCore(testArchive, options, (path, opts) => ReadImpl(path, expectedCompression, opts));
|
||||
}
|
||||
|
||||
options ??= new ReaderOptions() { BufferSize = 0x20000 }; //test larger buffer size (need test rather than eyeballing debug logs :P)
|
||||
private void ReadCore(
|
||||
string testArchive,
|
||||
ReaderOptions? options,
|
||||
Action<string, ReaderOptions> readImpl
|
||||
)
|
||||
{
|
||||
testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive);
|
||||
options ??= new ReaderOptions { BufferSize = 0x20000 };
|
||||
|
||||
options.LeaveStreamOpen = true;
|
||||
ReadImpl(testArchive, expectedCompression, options);
|
||||
readImpl(testArchive, options);
|
||||
|
||||
options.LeaveStreamOpen = false;
|
||||
ReadImpl(testArchive, expectedCompression, options);
|
||||
readImpl(testArchive, options);
|
||||
|
||||
VerifyFiles();
|
||||
}
|
||||
|
||||
private void ReadImpl(string testArchive, ReaderOptions options)
|
||||
{
|
||||
ReadImplCore(testArchive, options, UseReader);
|
||||
}
|
||||
|
||||
private void ReadImpl(
|
||||
string testArchive,
|
||||
CompressionType expectedCompression,
|
||||
ReaderOptions options
|
||||
)
|
||||
{
|
||||
ReadImplCore(testArchive, options, r => UseReader(r, expectedCompression));
|
||||
}
|
||||
|
||||
private void ReadImplCore(string testArchive, ReaderOptions options, Action<IReader> useReader)
|
||||
{
|
||||
using var file = File.OpenRead(testArchive);
|
||||
using var protectedStream = SharpCompressStream.Create(
|
||||
@@ -47,19 +71,17 @@ public abstract class ReaderTests : TestBase
|
||||
using var testStream = new TestStream(protectedStream);
|
||||
using (var reader = ReaderFactory.Open(testStream, options))
|
||||
{
|
||||
UseReader(reader, expectedCompression);
|
||||
useReader(reader);
|
||||
protectedStream.ThrowOnDispose = false;
|
||||
Assert.False(testStream.IsDisposed, $"{nameof(testStream)} prematurely closed");
|
||||
}
|
||||
|
||||
// Boolean XOR -- If the stream should be left open (true), then the stream should not be diposed (false)
|
||||
// and if the stream should be closed (false), then the stream should be disposed (true)
|
||||
var message =
|
||||
$"{nameof(options.LeaveStreamOpen)} is set to '{options.LeaveStreamOpen}', so {nameof(testStream.IsDisposed)} should be set to '{!testStream.IsDisposed}', but is set to {testStream.IsDisposed}";
|
||||
Assert.True(options.LeaveStreamOpen != testStream.IsDisposed, message);
|
||||
}
|
||||
|
||||
public void UseReader(IReader reader, CompressionType expectedCompression)
|
||||
protected void UseReader(IReader reader, CompressionType expectedCompression)
|
||||
{
|
||||
while (reader.MoveToNextEntry())
|
||||
{
|
||||
@@ -74,6 +96,20 @@ public abstract class ReaderTests : TestBase
|
||||
}
|
||||
}
|
||||
|
||||
private void UseReader(IReader reader)
|
||||
{
|
||||
while (reader.MoveToNextEntry())
|
||||
{
|
||||
if (!reader.Entry.IsDirectory)
|
||||
{
|
||||
reader.WriteEntryToDirectory(
|
||||
SCRATCH_FILES_PATH,
|
||||
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task ReadAsync(
|
||||
string testArchive,
|
||||
CompressionType expectedCompression,
|
||||
|
||||
BIN
tests/TestArchives/Archives/Arj.method4.arj
Normal file
BIN
tests/TestArchives/Archives/Arj.method4.arj
Normal file
Binary file not shown.
Reference in New Issue
Block a user