Properly integrated zip multivolume and split support.

This commit is contained in:
Craig
2022-04-30 19:35:40 +01:00
parent 910aa1c22e
commit 61c01ce9b0
18 changed files with 752 additions and 552 deletions

View File

@@ -1,8 +1,9 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Common;
using SharpCompress.IO;
using SharpCompress.Readers;
namespace SharpCompress.Archives
@@ -23,28 +24,14 @@ namespace SharpCompress.Archives
protected ReaderOptions ReaderOptions { get; }
private bool disposed;
protected SourceStream SrcStream;
internal AbstractArchive(ArchiveType type, FileInfo fileInfo, ReaderOptions readerOptions)
internal AbstractArchive(ArchiveType type, SourceStream srcStream)
{
Type = type;
if (!fileInfo.Exists)
{
throw new ArgumentException("File does not exist: " + fileInfo.FullName);
}
ReaderOptions = readerOptions;
readerOptions.LeaveStreamOpen = false;
lazyVolumes = new LazyReadOnlyCollection<TVolume>(LoadVolumes(fileInfo));
lazyEntries = new LazyReadOnlyCollection<TEntry>(LoadEntries(Volumes));
}
protected abstract IEnumerable<TVolume> LoadVolumes(FileInfo file);
internal AbstractArchive(ArchiveType type, IEnumerable<Stream> streams, ReaderOptions readerOptions)
{
Type = type;
ReaderOptions = readerOptions;
lazyVolumes = new LazyReadOnlyCollection<TVolume>(LoadVolumes(streams.Select(CheckStreams)));
ReaderOptions = srcStream.ReaderOptions;
SrcStream = srcStream;
lazyVolumes = new LazyReadOnlyCollection<TVolume>(LoadVolumes(SrcStream));
lazyEntries = new LazyReadOnlyCollection<TEntry>(LoadEntries(Volumes));
}
@@ -98,7 +85,7 @@ namespace SharpCompress.Archives
/// </summary>
public virtual long TotalUncompressSize => Entries.Aggregate(0L, (total, cf) => total + cf.Size);
protected abstract IEnumerable<TVolume> LoadVolumes(IEnumerable<Stream> streams);
protected abstract IEnumerable<TVolume> LoadVolumes(SourceStream srcStream);
protected abstract IEnumerable<TEntry> LoadEntries(IEnumerable<TVolume> volumes);
IEnumerable<IArchiveEntry> IArchive.Entries => Entries.Cast<IArchiveEntry>();
@@ -111,6 +98,8 @@ namespace SharpCompress.Archives
{
lazyVolumes.ForEach(v => v.Dispose());
lazyEntries.GetLoaded().Cast<Entry>().ForEach(x => x.Close());
if (SrcStream != null)
SrcStream.Dispose();
disposed = true;
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Common;
using SharpCompress.IO;
using SharpCompress.Readers;
using SharpCompress.Writers;
@@ -40,22 +41,11 @@ namespace SharpCompress.Archives
{
}
internal AbstractWritableArchive(ArchiveType type, Stream stream, ReaderOptions readerFactoryOptions)
: base(type, stream.AsEnumerable(), readerFactoryOptions)
internal AbstractWritableArchive(ArchiveType type, SourceStream srcStream)
: base(type, srcStream)
{
}
internal AbstractWritableArchive(ArchiveType type, FileInfo fileInfo, ReaderOptions readerFactoryOptions)
: base(type, fileInfo, readerFactoryOptions)
{
}
internal AbstractWritableArchive(ArchiveType type, IEnumerable<Stream> streams, ReaderOptions readerFactoryOptions)
: base(type, streams, readerFactoryOptions)
{
}
public override ICollection<TEntry> Entries
{
get

View File

@@ -137,34 +137,54 @@ namespace SharpCompress.Archives
switch (type.Value)
{
case ArchiveType.Zip:
using (Stream prt2z = files[1].OpenRead())
{
try
{
prt2z.Position += 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception
if (ZipArchive.IsZipFile(prt2z)) //if part2 is a zip then it's multi not split zip, z01,z02... zip is moved to the end when opened
return ZipArchive.Open(fileInfos.Select(a => a.OpenRead()), options);
}
catch { }
}
return ZipArchive.Open(new SplitStream(files), options);
return ZipArchive.Open(files, options);
case ArchiveType.SevenZip:
return SevenZipArchive.Open(new SplitStream(files), options);
return SevenZipArchive.Open(files, options);
case ArchiveType.GZip:
return GZipArchive.Open(new SplitStream(files), options);
return GZipArchive.Open(files, options);
case ArchiveType.Rar:
using (Stream prt2 = files[1].OpenRead())
{
try
{
if (RarArchive.IsRarFile(prt2, options)) //if part2 is a rar then it's multi not split
return RarArchive.Open(files.Select(f => File.OpenRead(f.FullName))); //multipart read
}
catch { }
}
return RarArchive.Open(new SplitStream(files), options);
return RarArchive.Open(files, options);
case ArchiveType.Tar:
return TarArchive.Open(new SplitStream(files), options);
return TarArchive.Open(files, options);
}
}
throw new InvalidOperationException("Cannot determine compressed stream type. Supported Archive Formats: Zip, GZip, Tar, Rar, 7Zip");
}
/// <summary>
/// Constructor with IEnumerable FileInfo objects, multi and split support.
/// </summary>
/// <param name="streams"></param>
/// <param name="options"></param>
public static IArchive Open(IEnumerable<Stream> streams, ReaderOptions? options = null)
{
streams.CheckNotNull(nameof(streams));
if (streams.Count() == 0)
throw new InvalidOperationException("No streams");
if (streams.Count() == 1)
return Open(streams.First(), options);
options ??= new ReaderOptions();
ArchiveType? type;
using (Stream stream = streams.First())
IsArchive(stream, out type); //test and reset stream position
if (type != null)
{
switch (type.Value)
{
case ArchiveType.Zip:
return ZipArchive.Open(streams, options);
case ArchiveType.SevenZip:
return SevenZipArchive.Open(streams, options);
case ArchiveType.GZip:
return GZipArchive.Open(streams, options);
case ArchiveType.Rar:
return RarArchive.Open(streams, options);
case ArchiveType.Tar:
return TarArchive.Open(streams, options);
}
}
throw new InvalidOperationException("Cannot determine compressed stream type. Supported Archive Formats: Zip, GZip, Tar, Rar, 7Zip");
@@ -235,5 +255,42 @@ namespace SharpCompress.Archives
return type != null;
}
/// <summary>
/// From a passed in archive (zip, rar, 7z, 001), return all parts.
/// </summary>
/// <param name="part1"></param>
/// <returns></returns>
public static IEnumerable<string> GetFileParts(string part1)
{
part1.CheckNotNullOrEmpty(nameof(part1));
return GetFileParts(new FileInfo(part1)).Select(a => a.FullName);
}
/// <summary>
/// From a passed in archive (zip, rar, 7z, 001), return all parts.
/// </summary>
/// <param name="part1"></param>
/// <returns></returns>
public static IEnumerable<FileInfo> GetFileParts(FileInfo part1)
{
part1.CheckNotNull(nameof(part1));
yield return part1;
int i = 1;
FileInfo? part = RarArchiveVolumeFactory.GetFilePart(i++, part1);
if (part != null)
{
yield return part;
while ((part = RarArchiveVolumeFactory.GetFilePart(i++, part1)) != null) //tests split too
yield return part;
}
else
{
i = 1;
while ((part = ZipArchiveVolumeFactory.GetFilePart(i++, part1)) != null) //tests split too
yield return part;
}
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.IO;
using SharpCompress.Readers;
using System.Linq;
using System.Text;
using SharpCompress.Common.Rar.Headers;
using System.Text.RegularExpressions;
namespace SharpCompress.Archives
{
internal abstract class ArchiveVolumeFactory
{
internal static FileInfo? GetFilePart(int index, FileInfo part1) //base the name on the first part
{
FileInfo? item = null;
//split 001, 002 ...
Match m = Regex.Match(part1.Name, @"^(.*\.)([0-9]+)$", RegexOptions.IgnoreCase);
if (m.Success)
item = new FileInfo(Path.Combine(part1.DirectoryName!, String.Concat(m.Groups[1].Value, (index + 1).ToString().PadLeft(m.Groups[2].Value.Length, '0'))));
if (item != null && item.Exists)
return item;
return null;
}
}
}

View File

@@ -1,9 +1,10 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Common;
using SharpCompress.Common.GZip;
using SharpCompress.IO;
using SharpCompress.Readers;
using SharpCompress.Readers.GZip;
using SharpCompress.Writers;
@@ -32,7 +33,31 @@ namespace SharpCompress.Archives.GZip
public static GZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.CheckNotNull(nameof(fileInfo));
return new GZipArchive(fileInfo, readerOptions ?? new ReaderOptions());
return new GZipArchive(new SourceStream(fileInfo, i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), readerOptions ?? new ReaderOptions()));
}
/// <summary>
/// Constructor with all file parts passed in
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static GZipArchive Open(IEnumerable<FileInfo> fileInfos, ReaderOptions? readerOptions = null)
{
fileInfos.CheckNotNull(nameof(fileInfos));
FileInfo[] files = fileInfos.ToArray();
return new GZipArchive(new SourceStream(files[0], i => i < files.Length ? files[i] : null, readerOptions ?? new ReaderOptions()));
}
/// <summary>
/// Constructor with all stream parts passed in
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
public static GZipArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
{
streams.CheckNotNull(nameof(streams));
Stream[] strms = streams.ToArray();
return new GZipArchive(new SourceStream(strms[0], i => i < strms.Length ? strms[i] : null, readerOptions ?? new ReaderOptions()));
}
/// <summary>
@@ -43,7 +68,7 @@ namespace SharpCompress.Archives.GZip
public static GZipArchive Open(Stream stream, ReaderOptions? readerOptions = null)
{
stream.CheckNotNull(nameof(stream));
return new GZipArchive(stream, readerOptions ?? new ReaderOptions());
return new GZipArchive(new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions()));
}
public static GZipArchive Create()
@@ -52,20 +77,20 @@ namespace SharpCompress.Archives.GZip
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// Constructor with a SourceStream able to handle FileInfo and Streams.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="srcStream"></param>
/// <param name="options"></param>
internal GZipArchive(FileInfo fileInfo, ReaderOptions options)
: base(ArchiveType.GZip, fileInfo, options)
internal GZipArchive(SourceStream srcStream)
: base(ArchiveType.Tar, srcStream)
{
}
protected override IEnumerable<GZipVolume> LoadVolumes(FileInfo file)
protected override IEnumerable<GZipVolume> LoadVolumes(SourceStream srcStream)
{
return new GZipVolume(file, ReaderOptions).AsEnumerable();
srcStream.LoadAllParts();
return srcStream.Streams.Select(a => new GZipVolume(a, ReaderOptions));
}
public static bool IsGZipFile(string filePath)
{
return IsGZipFile(new FileInfo(filePath));
@@ -114,16 +139,6 @@ namespace SharpCompress.Archives.GZip
return true;
}
/// <summary>
/// Takes multiple seekable Streams for a multi-part archive
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
internal GZipArchive(Stream stream, ReaderOptions options)
: base(ArchiveType.GZip, stream, options)
{
}
internal GZipArchive()
: base(ArchiveType.GZip)
{
@@ -160,11 +175,6 @@ namespace SharpCompress.Archives.GZip
}
}
protected override IEnumerable<GZipVolume> LoadVolumes(IEnumerable<Stream> streams)
{
return new GZipVolume(streams.First(), ReaderOptions).AsEnumerable();
}
protected override IEnumerable<GZipArchiveEntry> LoadEntries(IEnumerable<GZipVolume> volumes)
{
Stream stream = volumes.Single().Stream;

View File

@@ -5,6 +5,7 @@ using SharpCompress.Common;
using SharpCompress.Common.Rar;
using SharpCompress.Common.Rar.Headers;
using SharpCompress.Compressors.Rar;
using SharpCompress.IO;
using SharpCompress.Readers;
using SharpCompress.Readers.Rar;
@@ -16,28 +17,14 @@ namespace SharpCompress.Archives.Rar
internal Lazy<IRarUnpack> UnpackV2017 { get; } = new Lazy<IRarUnpack>(() => new SharpCompress.Compressors.Rar.UnpackV2017.Unpack());
internal Lazy<IRarUnpack> UnpackV1 { get; } = new Lazy<IRarUnpack>(() => new SharpCompress.Compressors.Rar.UnpackV1.Unpack());
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
internal RarArchive(FileInfo fileInfo, ReaderOptions options)
: base(ArchiveType.Rar, fileInfo, options)
{
}
protected override IEnumerable<RarVolume> LoadVolumes(FileInfo file)
{
return RarArchiveVolumeFactory.GetParts(file, ReaderOptions);
}
/// <summary>
/// Takes multiple seekable Streams for a multi-part archive
/// Constructor with a SourceStream able to handle FileInfo and Streams.
/// </summary>
/// <param name="streams"></param>
/// <param name="srcStream"></param>
/// <param name="options"></param>
internal RarArchive(IEnumerable<Stream> streams, ReaderOptions options)
: base(ArchiveType.Rar, streams, options)
internal RarArchive(SourceStream srcStream)
: base(ArchiveType.Rar, srcStream)
{
}
@@ -46,9 +33,20 @@ namespace SharpCompress.Archives.Rar
return RarArchiveEntryFactory.GetEntries(this, volumes, ReaderOptions);
}
protected override IEnumerable<RarVolume> LoadVolumes(IEnumerable<Stream> streams)
protected override IEnumerable<RarVolume> LoadVolumes(SourceStream srcStream)
{
return RarArchiveVolumeFactory.GetParts(streams, ReaderOptions);
base.SrcStream.LoadAllParts(); //request all streams
Stream[] streams = base.SrcStream.Streams.ToArray();
if (streams.Length > 1 && IsRarFile(streams[1], ReaderOptions)) //test part 2 - true = multipart not split
{
base.SrcStream.IsVolumes = true;
streams[1].Position = 0;
base.SrcStream.Position = 0;
return srcStream.Streams.Select(a => new StreamRarArchiveVolume(a, ReaderOptions));
}
else //split mode or single file
return new StreamRarArchiveVolume(base.SrcStream, ReaderOptions).AsEnumerable();
}
protected override IReader CreateReaderForSolidExtraction()
@@ -69,7 +67,8 @@ namespace SharpCompress.Archives.Rar
public static RarArchive Open(string filePath, ReaderOptions? options = null)
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
return new RarArchive(new FileInfo(filePath), options ?? new ReaderOptions());
FileInfo fileInfo = new FileInfo(filePath);
return new RarArchive(new SourceStream(fileInfo, i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo), options ?? new ReaderOptions()));
}
/// <summary>
@@ -80,7 +79,7 @@ namespace SharpCompress.Archives.Rar
public static RarArchive Open(FileInfo fileInfo, ReaderOptions? options = null)
{
fileInfo.CheckNotNull(nameof(fileInfo));
return new RarArchive(fileInfo, options ?? new ReaderOptions());
return new RarArchive(new SourceStream(fileInfo, i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo), options ?? new ReaderOptions()));
}
/// <summary>
@@ -91,20 +90,34 @@ namespace SharpCompress.Archives.Rar
public static RarArchive Open(Stream stream, ReaderOptions? options = null)
{
stream.CheckNotNull(nameof(stream));
return Open(stream.AsEnumerable(), options ?? new ReaderOptions());
return new RarArchive(new SourceStream(stream, i => null, options ?? new ReaderOptions()));
}
/// <summary>
/// Takes multiple seekable Streams for a multi-part archive
/// Constructor with all file parts passed in
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static RarArchive Open(IEnumerable<FileInfo> fileInfos, ReaderOptions? readerOptions = null)
{
fileInfos.CheckNotNull(nameof(fileInfos));
FileInfo[] files = fileInfos.ToArray();
return new RarArchive(new SourceStream(files[0], i => i < files.Length ? files[i] : null, readerOptions ?? new ReaderOptions()));
}
/// <summary>
/// Constructor with all stream parts passed in
/// </summary>
/// <param name="streams"></param>
/// <param name="options"></param>
public static RarArchive Open(IEnumerable<Stream> streams, ReaderOptions? options = null)
/// <param name="readerOptions"></param>
public static RarArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
{
streams.CheckNotNull(nameof(streams));
return new RarArchive(streams, options ?? new ReaderOptions());
Stream[] strms = streams.ToArray();
return new RarArchive(new SourceStream(strms[0], i => i < strms.Length ? strms[i] : null, readerOptions ?? new ReaderOptions()));
}
public static bool IsRarFile(string filePath)
{
return IsRarFile(new FileInfo(filePath));

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using SharpCompress.Common.Rar;
@@ -6,135 +6,35 @@ using SharpCompress.Readers;
using System.Linq;
using System.Text;
using SharpCompress.Common.Rar.Headers;
using System.Text.RegularExpressions;
namespace SharpCompress.Archives.Rar
{
internal static class RarArchiveVolumeFactory
{
internal static IEnumerable<RarVolume> GetParts(IEnumerable<Stream> streams, ReaderOptions options)
internal static FileInfo? GetFilePart(int index, FileInfo part1) //base the name on the first part
{
foreach (Stream s in streams)
{
if (!s.CanRead || !s.CanSeek)
{
throw new ArgumentException("Stream is not readable and seekable");
}
StreamRarArchiveVolume part = new StreamRarArchiveVolume(s, options);
yield return part;
}
}
FileInfo? item = null;
internal static IEnumerable<RarVolume> GetParts(FileInfo fileInfo, ReaderOptions options)
{
FileInfoRarArchiveVolume part = new FileInfoRarArchiveVolume(fileInfo, options);
yield return part;
ArchiveHeader ah = part.ArchiveHeader;
if (!ah.IsVolume)
{
yield break; //if file isn't volume then there is no reason to look
}
fileInfo = GetNextFileInfo(ah, part.FileParts.FirstOrDefault() as FileInfoRarFilePart)!;
//we use fileinfo because rar is dumb and looks at file names rather than archive info for another volume
while (fileInfo != null && fileInfo.Exists)
{
part = new FileInfoRarArchiveVolume(fileInfo, options);
fileInfo = GetNextFileInfo(ah, part.FileParts.FirstOrDefault() as FileInfoRarFilePart)!;
yield return part;
}
}
private static FileInfo? GetNextFileInfo(ArchiveHeader ah, FileInfoRarFilePart? currentFilePart)
{
if (currentFilePart is null)
{
return null;
}
bool oldNumbering = ah.OldNumberingFormat
|| currentFilePart.MarkHeader.OldNumberingFormat;
if (oldNumbering)
{
return FindNextFileWithOldNumbering(currentFilePart.FileInfo);
}
//new style rar - ..part1 | /part01 | part001 ....
Match m = Regex.Match(part1.Name, @"^(.*\.part)([0-9]+)(\.rar)$", RegexOptions.IgnoreCase);
if (m.Success)
item = new FileInfo(Path.Combine(part1.DirectoryName!, String.Concat(m.Groups[1].Value, (index + 1).ToString().PadLeft(m.Groups[2].Value.Length, '0'), m.Groups[3].Value)));
else
{
return FindNextFileWithNewNumbering(currentFilePart.FileInfo);
//old style - ...rar, .r00, .r01 ...
m = Regex.Match(part1.Name, @"^(.*\.r)(ar|[0-9]+)$", RegexOptions.IgnoreCase);
if (m.Success)
item = new FileInfo(Path.Combine(part1.DirectoryName!, String.Concat(m.Groups[1].Value, index == 0 ? "ar" : (index - 1).ToString().PadLeft(m.Groups[2].Value.Length, '0'))));
else //split .001, .002 ....
return ArchiveVolumeFactory.GetFilePart(index, part1);
}
if (item != null && item.Exists)
return item;
return null; //no more items
}
private static FileInfo FindNextFileWithOldNumbering(FileInfo currentFileInfo)
{
// .rar, .r00, .r01, ...
string extension = currentFileInfo.Extension;
var buffer = new StringBuilder(currentFileInfo.FullName.Length);
buffer.Append(currentFileInfo.FullName.Substring(0,
currentFileInfo.FullName.Length - extension.Length));
if (string.Equals(extension, ".rar", StringComparison.OrdinalIgnoreCase))
{
buffer.Append(".r00");
}
else
{
if (int.TryParse(extension.Substring(2, 2), out int num))
{
num++;
buffer.Append(".r");
if (num < 10)
{
buffer.Append('0');
}
buffer.Append(num);
}
else
{
ThrowInvalidFileName(currentFileInfo);
}
}
return new FileInfo(buffer.ToString());
}
private static FileInfo FindNextFileWithNewNumbering(FileInfo currentFileInfo)
{
// part1.rar, part2.rar, ...
string extension = currentFileInfo.Extension;
if (!string.Equals(extension, ".rar", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("Invalid extension, expected 'rar': " + currentFileInfo.FullName);
}
int startIndex = currentFileInfo.FullName.LastIndexOf(".part", StringComparison.OrdinalIgnoreCase);
if (startIndex < 0)
{
ThrowInvalidFileName(currentFileInfo);
}
StringBuilder buffer = new StringBuilder(currentFileInfo.FullName.Length);
buffer.Append(currentFileInfo.FullName, 0, startIndex);
string numString = currentFileInfo.FullName.Substring(startIndex + 5,
currentFileInfo.FullName.IndexOf('.', startIndex + 5) -
startIndex - 5);
buffer.Append(".part");
if (int.TryParse(numString, out int num))
{
num++;
for (int i = 0; i < numString.Length - num.ToString().Length; i++)
{
buffer.Append('0');
}
buffer.Append(num);
}
else
{
ThrowInvalidFileName(currentFileInfo);
}
buffer.Append(".rar");
return new FileInfo(buffer.ToString());
}
private static void ThrowInvalidFileName(FileInfo fileInfo)
{
throw new ArgumentException("Filename invalid or next archive could not be found:"
+ fileInfo.FullName);
}
}
}

View File

@@ -34,8 +34,33 @@ namespace SharpCompress.Archives.SevenZip
public static SevenZipArchive Open(FileInfo fileInfo, ReaderOptions readerOptions = null)
{
fileInfo.CheckNotNull("fileInfo");
return new SevenZipArchive(fileInfo, readerOptions ?? new ReaderOptions());
return new SevenZipArchive(new SourceStream(fileInfo, i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), readerOptions ?? new ReaderOptions()));
}
/// <summary>
/// Constructor with all file parts passed in
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static SevenZipArchive Open(IEnumerable<FileInfo> fileInfos, ReaderOptions readerOptions = null)
{
fileInfos.CheckNotNull(nameof(fileInfos));
FileInfo[] files = fileInfos.ToArray();
return new SevenZipArchive(new SourceStream(files[0], i => i < files.Length ? files[i] : null, readerOptions ?? new ReaderOptions()));
}
/// <summary>
/// Constructor with all stream parts passed in
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
public static SevenZipArchive Open(IEnumerable<Stream> streams, ReaderOptions readerOptions = null)
{
streams.CheckNotNull(nameof(streams));
Stream[] strms = streams.ToArray();
return new SevenZipArchive(new SourceStream(strms[0], i => i < strms.Length ? strms[i] : null, readerOptions ?? new ReaderOptions()));
}
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
@@ -44,17 +69,23 @@ namespace SharpCompress.Archives.SevenZip
public static SevenZipArchive Open(Stream stream, ReaderOptions readerOptions = null)
{
stream.CheckNotNull("stream");
return new SevenZipArchive(stream, readerOptions ?? new ReaderOptions());
return new SevenZipArchive(new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions()));
}
internal SevenZipArchive(FileInfo fileInfo, ReaderOptions readerOptions)
: base(ArchiveType.SevenZip, fileInfo, readerOptions)
/// <summary>
/// Constructor with a SourceStream able to handle FileInfo and Streams.
/// </summary>
/// <param name="srcStream"></param>
/// <param name="options"></param>
internal SevenZipArchive(SourceStream srcStream)
: base(ArchiveType.SevenZip, srcStream)
{
}
protected override IEnumerable<SevenZipVolume> LoadVolumes(FileInfo file)
protected override IEnumerable<SevenZipVolume> LoadVolumes(SourceStream srcStream)
{
return new SevenZipVolume(file.OpenRead(), ReaderOptions).AsEnumerable();
base.SrcStream.LoadAllParts(); //request all streams
return new SevenZipVolume(srcStream, ReaderOptions).AsEnumerable(); //simple single volume or split, multivolume not supported
}
public static bool IsSevenZipFile(string filePath)
@@ -74,29 +105,11 @@ namespace SharpCompress.Archives.SevenZip
}
}
internal SevenZipArchive(Stream stream, ReaderOptions readerOptions)
: base(ArchiveType.SevenZip, stream.AsEnumerable(), readerOptions)
{
}
internal SevenZipArchive()
: base(ArchiveType.SevenZip)
{
}
protected override IEnumerable<SevenZipVolume> LoadVolumes(IEnumerable<Stream> streams)
{
foreach (Stream s in streams)
{
if (!s.CanRead || !s.CanSeek)
{
throw new ArgumentException("Stream is not readable and seekable");
}
SevenZipVolume volume = new SevenZipVolume(s, ReaderOptions);
yield return volume;
}
}
protected override IEnumerable<SevenZipArchiveEntry> LoadEntries(IEnumerable<SevenZipVolume> volumes)
{
var stream = volumes.Single().Stream;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -34,7 +34,31 @@ namespace SharpCompress.Archives.Tar
public static TarArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.CheckNotNull(nameof(fileInfo));
return new TarArchive(fileInfo, readerOptions ?? new ReaderOptions());
return new TarArchive(new SourceStream(fileInfo, i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), readerOptions ?? new ReaderOptions()));
}
/// <summary>
/// Constructor with all file parts passed in
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static TarArchive Open(IEnumerable<FileInfo> fileInfos, ReaderOptions? readerOptions = null)
{
fileInfos.CheckNotNull(nameof(fileInfos));
FileInfo[] files = fileInfos.ToArray();
return new TarArchive(new SourceStream(files[0], i => i < files.Length ? files[i] : null, readerOptions ?? new ReaderOptions()));
}
/// <summary>
/// Constructor with all stream parts passed in
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
public static TarArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
{
streams.CheckNotNull(nameof(streams));
Stream[] strms = streams.ToArray();
return new TarArchive(new SourceStream(strms[0], i => i < strms.Length ? strms[i] : null, readerOptions ?? new ReaderOptions()));
}
/// <summary>
@@ -45,7 +69,7 @@ namespace SharpCompress.Archives.Tar
public static TarArchive Open(Stream stream, ReaderOptions? readerOptions = null)
{
stream.CheckNotNull(nameof(stream));
return new TarArchive(stream, readerOptions ?? new ReaderOptions());
return new TarArchive(new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions()));
}
public static bool IsTarFile(string filePath)
@@ -80,28 +104,19 @@ namespace SharpCompress.Archives.Tar
return false;
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="readerOptions"></param>
internal TarArchive(FileInfo fileInfo, ReaderOptions readerOptions)
: base(ArchiveType.Tar, fileInfo, readerOptions)
protected override IEnumerable<TarVolume> LoadVolumes(SourceStream srcStream)
{
}
protected override IEnumerable<TarVolume> LoadVolumes(FileInfo file)
{
return new TarVolume(file.OpenRead(), ReaderOptions).AsEnumerable();
base.SrcStream.LoadAllParts(); //request all streams
return new TarVolume(srcStream, ReaderOptions).AsEnumerable(); //simple single volume or split, multivolume not supported
}
/// <summary>
/// Takes multiple seekable Streams for a multi-part archive
/// Constructor with a SourceStream able to handle FileInfo and Streams.
/// </summary>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
internal TarArchive(Stream stream, ReaderOptions readerOptions)
: base(ArchiveType.Tar, stream, readerOptions)
/// <param name="srcStream"></param>
/// <param name="options"></param>
internal TarArchive(SourceStream srcStream)
: base(ArchiveType.Tar, srcStream)
{
}
@@ -110,11 +125,6 @@ namespace SharpCompress.Archives.Tar
{
}
protected override IEnumerable<TarVolume> LoadVolumes(IEnumerable<Stream> streams)
{
return new TarVolume(streams.First(), ReaderOptions).AsEnumerable();
}
protected override IEnumerable<TarArchiveEntry> LoadEntries(IEnumerable<TarVolume> volumes)
{
Stream stream = volumes.Single().Stream;

View File

@@ -26,6 +26,17 @@ namespace SharpCompress.Archives.Zip
/// </summary>
public CompressionLevel DeflateCompressionLevel { get; set; }
/// <summary>
/// Constructor with a SourceStream able to handle FileInfo and Streams.
/// </summary>
/// <param name="srcStream"></param>
/// <param name="options"></param>
internal ZipArchive(SourceStream srcStream)
: base(ArchiveType.Zip, srcStream)
{
headerFactory = new SeekableZipHeaderFactory(srcStream.ReaderOptions.Password, srcStream.ReaderOptions.ArchiveEncoding);
}
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
@@ -45,32 +56,33 @@ namespace SharpCompress.Archives.Zip
public static ZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.CheckNotNull(nameof(fileInfo));
return new ZipArchive(fileInfo, readerOptions ?? new ReaderOptions());
return new ZipArchive(new SourceStream(fileInfo, i => ZipArchiveVolumeFactory.GetFilePart(i, fileInfo), readerOptions ?? new ReaderOptions()));
}
/// <summary>
/// Takes multiple seekable Streams for a multi-part archive
/// Constructor with all file parts passed in
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static ZipArchive Open(IEnumerable<FileInfo> fileInfos, ReaderOptions? readerOptions = null)
{
fileInfos.CheckNotNull(nameof(fileInfos));
FileInfo[] files = fileInfos.ToArray();
return new ZipArchive(new SourceStream(files[0], i => i < files.Length ? files[i] : null, readerOptions ?? new ReaderOptions()));
}
/// <summary>
/// Constructor with all stream parts passed in
/// </summary>
/// <param name="streams"></param>
/// <param name="options"></param>
public static ZipArchive Open(IEnumerable<Stream> streams, ReaderOptions? options = null)
/// <param name="readerOptions"></param>
public static ZipArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
{
streams.CheckNotNull(nameof(streams));
return new ZipArchive(streams, options ?? new ReaderOptions());
Stream[] strms = streams.ToArray();
return new ZipArchive(new SourceStream(strms[0], i => i < strms.Length ? strms[i] : null, readerOptions ?? new ReaderOptions()));
}
/// <summary>
/// Takes multiple seekable Streams for a multi-part archive
/// </summary>
/// <param name="streams"></param>
/// <param name="options"></param>
internal ZipArchive(IEnumerable<Stream> streams, ReaderOptions options)
: base(ArchiveType.Zip, streams, options)
{
headerFactory = new SeekableZipHeaderFactory(options.Password, options.ArchiveEncoding);
}
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
@@ -79,7 +91,7 @@ namespace SharpCompress.Archives.Zip
public static ZipArchive Open(Stream stream, ReaderOptions? readerOptions = null)
{
stream.CheckNotNull(nameof(stream));
return new ZipArchive(stream, readerOptions ?? new ReaderOptions());
return new ZipArchive(new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions()));
}
public static bool IsZipFile(string filePath, string? password = null)
@@ -150,20 +162,32 @@ namespace SharpCompress.Archives.Zip
}
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="readerOptions"></param>
internal ZipArchive(FileInfo fileInfo, ReaderOptions readerOptions)
: base(ArchiveType.Zip, fileInfo, readerOptions)
protected override IEnumerable<ZipVolume> LoadVolumes(SourceStream srcStream)
{
headerFactory = new SeekableZipHeaderFactory(readerOptions.Password, readerOptions.ArchiveEncoding);
}
base.SrcStream.LoadAllParts(); //request all streams
base.SrcStream.Position = 0;
protected override IEnumerable<ZipVolume> LoadVolumes(FileInfo file)
{
return new ZipVolume(file.OpenRead(), ReaderOptions).AsEnumerable();
List<Stream> streams = base.SrcStream.Streams.ToList();
if (streams.Count > 1) //test part 2 - true = multipart not split
{
streams[1].Position += 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception
bool isZip = IsZipFile(streams[1], ReaderOptions.Password);
streams[1].Position -= 4;
if (isZip)
{
base.SrcStream.IsVolumes = true;
var tmp = streams[0]; //arcs as zip, z01 ... swap the zip the end
streams.RemoveAt(0);
streams.Add(tmp);
//streams[0].Position = 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception
return streams.Select(a => new ZipVolume(a, ReaderOptions));
}
}
//split mode or single file
return new ZipVolume(base.SrcStream, ReaderOptions).AsEnumerable();
}
internal ZipArchive()
@@ -171,28 +195,6 @@ namespace SharpCompress.Archives.Zip
{
}
/// <summary>
/// Takes multiple seekable Streams for a multi-part archive
/// </summary>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
internal ZipArchive(Stream stream, ReaderOptions readerOptions)
: base(ArchiveType.Zip, stream, readerOptions)
{
headerFactory = new SeekableZipHeaderFactory(readerOptions.Password, readerOptions.ArchiveEncoding);
}
protected override IEnumerable<ZipVolume> LoadVolumes(IEnumerable<Stream> streams)
{
var st = streams.ToList(); //swap the zip to the end
var tmp = st[0];
st.RemoveAt(0);
st.Add(tmp);
foreach (var s in st)
yield return new ZipVolume(s, base.ReaderOptions);
}
protected override IEnumerable<ZipArchiveEntry> LoadEntries(IEnumerable<ZipVolume> volumes)
{
var vols = volumes.ToArray();
@@ -207,7 +209,10 @@ namespace SharpCompress.Archives.Zip
DirectoryEntryHeader deh = (DirectoryEntryHeader)h;
Stream s;
if (deh.RelativeOffsetOfEntryHeader + deh.CompressedSize > vols[deh.DiskNumberStart].Stream.Length)
s = new SplitStream(vols.Skip(deh.DiskNumberStart).Select(a => a.Stream));
{
var v = vols.Skip(deh.DiskNumberStart).ToArray();
s = new SourceStream(v[0].Stream, i => i < v.Length ? v[i].Stream : null, new ReaderOptions() { LeaveStreamOpen = true });
}
else
s = vols[deh.DiskNumberStart].Stream;
yield return new ZipArchiveEntry(this, new SeekableZipFilePart(headerFactory, deh, s));

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.IO;
using SharpCompress.Common.Rar;
using SharpCompress.Readers;
using System.Linq;
using System.Text;
using SharpCompress.Common.Rar.Headers;
using System.Text.RegularExpressions;
namespace SharpCompress.Archives.Zip
{
internal static class ZipArchiveVolumeFactory
{
internal static FileInfo? GetFilePart(int index, FileInfo part1) //base the name on the first part
{
FileInfo? item = null;
//load files with zip/zipx first. Swapped to end once loaded in ZipArchive
//new style .zip, z01.. | .zipx, zx01 - if the numbers go beyond 99 then they use 100 ...1000 etc
Match m = Regex.Match(part1.Name, @"^(.*\.)(zipx?|zx?[0-9]+)$", RegexOptions.IgnoreCase);
if (m.Success)
item = new FileInfo(Path.Combine(part1.DirectoryName!, String.Concat(m.Groups[1].Value, Regex.Replace(m.Groups[2].Value, @"[^xz]", ""), index.ToString().PadLeft(2, '0'))));
else //split - 001, 002 ...
return ArchiveVolumeFactory.GetFilePart(index, part1);
if (item != null && item.Exists)
return item;
return null; //no more items
}
}
}

View File

@@ -0,0 +1,215 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Readers;
namespace SharpCompress.IO
{
public class SourceStream : Stream
{
private long _prevSize;
private List<FileInfo> _files;
private List<Stream> _streams;
private Func<int, FileInfo?> _getFilePart;
private Func<int, Stream?> _getStreamPart;
private int _stream;
public SourceStream(FileInfo file, Func<int, FileInfo?> getPart, ReaderOptions options) : this(null, null, file, getPart, options)
{
}
public SourceStream(Stream stream, Func<int, Stream?> getPart, ReaderOptions options) : this(stream, getPart, null, null, options)
{
}
private SourceStream(Stream? stream, Func<int, Stream?>? getStreamPart, FileInfo? file, Func<int, FileInfo?>? getFilePart, ReaderOptions options)
{
this.ReaderOptions = options;
_files = new List<FileInfo>();
_streams = new List<Stream>();
IsFileMode = file != null;
IsVolumes = false;
if (!IsFileMode)
{
_streams.Add(stream!);
_getStreamPart = getStreamPart!;
_getFilePart = new Func<int, FileInfo>(a => null!);
if (stream! is FileStream)
_files.Add(new FileInfo(((FileStream)stream!).Name));
}
else
{
_files.Add(file!);
_streams.Add(_files[0].OpenRead());
_getFilePart = getFilePart!;
_getStreamPart = new Func<int, Stream>(a => null!);
}
_stream = 0;
_prevSize = 0;
}
public void LoadAllParts()
{
for (int i = 1; SetStream(i); i++)
{
}
SetStream(0);
}
public bool IsVolumes { get; set; }
public ReaderOptions ReaderOptions { get; }
public bool IsFileMode { get; }
public IEnumerable<FileInfo> Files => _files;
public IEnumerable<Stream> Streams => _streams;
private Stream Current => _streams[_stream];
public bool LoadStream(int index) //ensure all parts to id are loaded
{
while (_streams.Count <= index)
{
if (IsFileMode)
{
FileInfo? f = _getFilePart(_streams.Count);
if (f == null)
{
_stream = _streams.Count - 1;
return false;
}
//throw new Exception($"File part {idx} not available.");
_files.Add(f);
_streams.Add(_files.Last().OpenRead());
}
else
{
Stream? s = _getStreamPart(_streams.Count);
if (s == null)
{
_stream = _streams.Count - 1;
return false;
}
//throw new Exception($"Stream part {idx} not available.");
_streams.Add(s);
if (s is FileStream)
_files.Add(new FileInfo(((FileStream)s).Name));
}
}
return true;
}
public bool SetStream(int idx) //allow caller to switch part in multipart
{
if (LoadStream(idx))
_stream = idx;
return _stream == idx;
}
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => false;
public override long Length => (!IsVolumes ? _streams.Sum(a => a.Length) : Current.Length);
public override long Position
{
get => _prevSize + Current.Position; //_prevSize is 0 for multivolume
set => Seek(value, SeekOrigin.Begin);
}
public override void Flush()
{
Current.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
if (count <= 0)
return 0;
int total = count;
int r = -1;
while (count != 0 && r != 0)
{
r = Current.Read(buffer, offset, (int)Math.Min(count, Current.Length - Current.Position));
count -= r;
offset += r;
if (!IsVolumes && count != 0 && Current.Position == Current.Length)
{
_prevSize += Current.Length;
SetStream(_stream + 1); //will load next file
Current.Seek(0, SeekOrigin.Begin);
}
}
return total - count;
}
public override long Seek(long offset, SeekOrigin origin)
{
long pos = this.Position;
switch (origin)
{
case SeekOrigin.Begin: pos = offset; break;
case SeekOrigin.Current: pos += offset; break;
case SeekOrigin.End: pos = Length + offset; break;
}
_prevSize = 0;
if (!IsVolumes)
{
SetStream(0);
while (_prevSize + Current.Length < pos)
{
_prevSize += Current.Length;
SetStream(_stream + 1);
}
}
if (pos != _prevSize + Current.Position)
Current.Seek(pos - _prevSize, SeekOrigin.Begin);
return pos;
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override void Close()
{
if (this.IsFileMode || !this.ReaderOptions.LeaveStreamOpen) //close if file mode or options specify it
{
foreach (Stream stream in _streams)
{
try
{
if (stream != null)
stream.Dispose();
}
catch { }
}
_streams.Clear();
_files.Clear();
}
}
protected override void Dispose(bool disposing)
{
this.Close();
base.Dispose(disposing);
}
}
}

View File

@@ -1,186 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharpCompress.IO
{
public class SplitStream : Stream
{
private int _idx;
private long _size;
private bool _isSplit;
private long _prevSize;
private long _pos;
private long _streamCount;
private FileInfo[]? _files;
private Stream[]? _streams;
private Stream _stream;
public SplitStream(IEnumerable<FileInfo> files) : this(null, files)
{
}
public SplitStream(IEnumerable<Stream> streams) : this(streams, null)
{
}
private SplitStream(IEnumerable<Stream>? streams, IEnumerable<FileInfo>? files)
{
if (streams != null)
{
_streams = streams.ToArray();
_isSplit = _streams.Length > 1;
_size = _streams.Sum(a => a.Length);
_streamCount = _streams.Length;
}
else if (files != null)
{
_files = files.ToArray();
_isSplit = _files.Length > 1;
_size = _files.Sum(a => a.Length);
_streamCount = _files.Length;
}
_idx = 0;
_prevSize = 0;
_stream = OpenStream(0);
}
private Stream OpenStream(int idx)
{
if (_streams != null)
_stream = _streams[idx];
else if (_files != null)
{
if (_stream != null)
_stream.Dispose();
_stream = File.OpenRead(_files[idx].FullName);
}
_idx = idx;
_pos = 0;
return _stream;
}
private long StreamLen(int idx)
{
if (_streams != null)
return _streams[idx].Length;
else if (_files != null)
return _files[idx].Length;
return 0;
}
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => false;
public override long Length => _size;
public override long Position
{
get => _prevSize + _pos;
set => Seek(value, SeekOrigin.Begin);
}
public override void Flush()
{
_stream.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
count = (int)Math.Min(count, _size - this.Position);
if (count <= 0)
return 0;
int total = count;
int r = -1;
while (count != 0 && r != 0)
{
r = _stream.Read(buffer, offset, count);
_pos += (long)r;
count -= r;
offset += r;
if (_isSplit && _pos == StreamLen(_idx))
Seek(0, SeekOrigin.Current); //will load next file
}
return total - count;
}
public override long Seek(long offset, SeekOrigin origin)
{
long pos = this.Position;
switch (origin)
{
case SeekOrigin.Begin: pos = offset; break;
case SeekOrigin.Current: pos += offset; break;
case SeekOrigin.End: pos = Length + offset; break;
}
if (_isSplit)
{
_prevSize = 0;
for (int i = 0; i < _streamCount; i++)
{
if (_prevSize + StreamLen(i) > pos)
{
if (_idx != i)
_stream = OpenStream(i);
break;
}
_prevSize += StreamLen(i);
}
}
_pos = pos - _prevSize;
if (_pos != _stream.Position && this.Position != this.Length)
_stream.Seek(_pos, SeekOrigin.Begin);
return pos;
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override void Close()
{
try
{
if (_stream != null)
_stream.Close();
}
catch { }
base.Close();
}
protected override void Dispose(bool disposing)
{
try
{
if (_stream != null)
_stream.Dispose();
}
catch { }
base.Dispose(disposing);
}
}
}

View File

@@ -26,6 +26,8 @@
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<IsTrimmable>true</IsTrimmable>
<RunAnalyzersDuringLiveAnalysis>False</RunAnalyzersDuringLiveAnalysis>
<RunAnalyzersDuringBuild>False</RunAnalyzersDuringBuild>
</PropertyGroup>
<ItemGroup>

View File

@@ -12,6 +12,15 @@ namespace SharpCompress.Test
{
public class ArchiveTests : ReaderTests
{
protected void ArchiveGetParts(IEnumerable<string> testArchives)
{
string[] arcs = testArchives.Select(a => Path.Combine(TEST_ARCHIVES_PATH, a)).ToArray();
string[] found = ArchiveFactory.GetFileParts(arcs[0]).ToArray();
Assert.Equal(arcs.Length, found.Length);
for (int i = 0; i < arcs.Length; i++)
Assert.Equal(arcs[i], found[i]);
}
protected void ArchiveStreamReadExtractAll(string testArchive, CompressionType compression)
{
testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive);
@@ -95,38 +104,6 @@ namespace SharpCompress.Test
}
}
protected void ArchiveStreamSplitRead(ReaderOptions readerOptions = null, params string[] testArchives)
{
ArchiveStreamSplitRead(readerOptions, testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x)));
}
protected void ArchiveStreamSplitRead(ReaderOptions readerOptions, IEnumerable<string> testArchives)
{
using (SplitStream stream = new SplitStream(testArchives.Select(f => new FileInfo(f))))
{
using (var archive = ArchiveFactory.Open(stream, readerOptions))
{
try
{
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
{
entry.WriteToDirectory(SCRATCH_FILES_PATH,
new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
}
}
catch (IndexOutOfRangeException)
{
throw;
}
}
VerifyFiles();
}
}
protected void ArchiveStreamMultiRead(ReaderOptions readerOptions = null, params string[] testArchives)
{
ArchiveStreamMultiRead(readerOptions, testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x)));

View File

@@ -435,6 +435,45 @@ namespace SharpCompress.Test.Rar
ArchiveFileRead("Rar4.rar");
}
[Fact]
public void Rar_GetPartsSplit()
{
//uses first part to search for all parts and compares against this array
ArchiveGetParts(new string[] {
"Rar4.split.001",
"Rar4.split.002",
"Rar4.split.003",
"Rar4.split.004",
"Rar4.split.005",
"Rar4.split.006"});
}
[Fact]
public void Rar_GetPartsOld()
{
//uses first part to search for all parts and compares against this array
ArchiveGetParts(new string[] {
"Rar2.multi.rar",
"Rar2.multi.r00",
"Rar2.multi.r01",
"Rar2.multi.r02",
"Rar2.multi.r03",
"Rar2.multi.r04",
"Rar2.multi.r05"});
}
[Fact]
public void Rar_GetPartsNew()
{
//uses first part to search for all parts and compares against this array
ArchiveGetParts(new string[] {
"Rar4.multi.part01.rar",
"Rar4.multi.part02.rar",
"Rar4.multi.part03.rar",
"Rar4.multi.part04.rar",
"Rar4.multi.part05.rar",
"Rar4.multi.part06.rar",
"Rar4.multi.part07.rar"});
}
[Fact]
public void Rar4_Multi_ArchiveStreamRead()
{
@@ -452,7 +491,7 @@ namespace SharpCompress.Test.Rar
[Fact]
public void Rar4_Split_ArchiveStreamRead()
{
ArchiveStreamSplitRead(null, new string[] {
ArchiveStreamMultiRead(null, new string[] {
"Rar4.split.001",
"Rar4.split.002",
"Rar4.split.003",
@@ -460,6 +499,42 @@ namespace SharpCompress.Test.Rar
"Rar4.split.005",
"Rar4.split.006"});
}
//will detect and load other files
[Fact]
public void Rar4_Multi_ArchiveFirstFileRead()
{
ArchiveFileRead("Rar4.multi.part01.rar");
//"Rar4.multi.part02.rar",
//"Rar4.multi.part03.rar",
//"Rar4.multi.part04.rar",
//"Rar4.multi.part05.rar",
//"Rar4.multi.part06.rar",
//"Rar4.multi.part07.rar"
}
//will detect and load other files
[Fact]
public void Rar4_Split_ArchiveFirstFileRead()
{
ArchiveFileRead("Rar4.split.001");
//"Rar4.split.002",
//"Rar4.split.003",
//"Rar4.split.004",
//"Rar4.split.005",
//"Rar4.split.006"
}
//will detect and load other files
[Fact]
public void Rar4_Split_ArchiveStreamFirstFileRead()
{
ArchiveStreamMultiRead(null, new string[] {
"Rar4.split.001",
//"Rar4.split.002",
//"Rar4.split.003",
//"Rar4.split.004",
//"Rar4.split.005",
//"Rar4.split.006"
});
}
//open with ArchiveFactory.Open and stream
[Fact]

View File

@@ -116,7 +116,7 @@ namespace SharpCompress.Test.SevenZip
[Fact]
public void SevenZipArchive_BZip2_Split_Working()
{
ArchiveStreamSplitRead(null, "7Zip.BZip2.split.001",
ArchiveStreamMultiRead(null, "7Zip.BZip2.split.001",
"7Zip.BZip2.split.002",
"7Zip.BZip2.split.003",
"7Zip.BZip2.split.004",
@@ -125,5 +125,19 @@ namespace SharpCompress.Test.SevenZip
"7Zip.BZip2.split.007");
}
//will detect and load other files
[Fact]
public void SevenZipArchive_BZip2_Split_FirstFileRead()
{
ArchiveFileRead("7Zip.BZip2.split.001");
//"7Zip.BZip2.split.002",
//"7Zip.BZip2.split.003",
//"7Zip.BZip2.split.004",
//"7Zip.BZip2.split.005",
//"7Zip.BZip2.split.006",
//"7Zip.BZip2.split.007"
}
}
}

View File

@@ -134,10 +134,63 @@ namespace SharpCompress.Test.Zip
{
ArchiveFileRead("Zip.deflate.zip");
}
//will detect and load other files
[Fact]
public void Zip_Deflate_Multi_ArchiveFirstFileRead()
{
ArchiveFileRead("WinZip26.nocomp.multi.zip");
//"WinZip26.nocomp.multi.z01"
}
//will detect and load other files
[Fact]
public void ZipX_Deflate_Multi_ArchiveFirstFileRead()
{
ArchiveFileRead("WinZip26.nocomp.multi.zipx");
//"WinZip26.nocomp.multi.zx01"
}
[Fact]
public void Zip_GetParts()
{
//uses first part to search for all parts and compares against this array
ArchiveGetParts(new string[] {
"Infozip.nocomp.multi.zip",
"Infozip.nocomp.multi.z01"});
}
[Fact]
public void ZipX_GetParts()
{
//uses first part to search for all parts and compares against this array
ArchiveGetParts(new string[] {
"WinZip26.nocomp.multi.zipx",
"WinZip26.nocomp.multi.zx01"});
}
[Fact]
public void Zip_GetPartsSplit()
{
//uses first part to search for all parts and compares against this array
ArchiveGetParts(new string[] {
"Zip.deflate.split.001",
"Zip.deflate.split.002",
"Zip.deflate.split.003",
"Zip.deflate.split.004",
"Zip.deflate.split.005",
"Zip.deflate.split.006"});
}
//will detect and load other files
[Fact]
public void Zip_Deflate_Split_ArchiveFirstFileRead()
{
ArchiveFileRead("Zip.deflate.split.001");
//"Zip.deflate.split.002",
//"Zip.deflate.split.003",
//"Zip.deflate.split.004",
//"Zip.deflate.split.005",
//"Zip.deflate.split.006"
}
[Fact]
public void Zip_Deflate_Split_ArchiveFileRead()
{
ArchiveStreamSplitRead(null, "Zip.deflate.split.001",
ArchiveStreamMultiRead(null, "Zip.deflate.split.001",
"Zip.deflate.split.002",
"Zip.deflate.split.003",
"Zip.deflate.split.004",