From 61c01ce9b083ed9c23229ee4d75975cfa0aa58e1 Mon Sep 17 00:00:00 2001 From: Craig Date: Sat, 30 Apr 2022 19:35:40 +0100 Subject: [PATCH] Properly integrated zip multivolume and split support. --- src/SharpCompress/Archives/AbstractArchive.cs | 31 +-- .../Archives/AbstractWritableArchive.cs | 16 +- src/SharpCompress/Archives/ArchiveFactory.cs | 105 +++++++-- .../Archives/ArchiveVolumeFactory.cs | 29 +++ .../Archives/GZip/GZipArchive.cs | 60 +++-- src/SharpCompress/Archives/Rar/RarArchive.cs | 67 +++--- .../Archives/Rar/RarArchiveVolumeFactory.cs | 138 ++--------- .../Archives/SevenZip/SevenZipArchive.cs | 61 +++-- src/SharpCompress/Archives/Tar/TarArchive.cs | 60 +++-- src/SharpCompress/Archives/Zip/ZipArchive.cs | 111 ++++----- .../Archives/Zip/ZipArchiveVolumeFactory.cs | 34 +++ src/SharpCompress/IO/SourceStream.cs | 215 ++++++++++++++++++ src/SharpCompress/IO/SplitStream.cs | 186 --------------- src/SharpCompress/SharpCompress.csproj | 2 + tests/SharpCompress.Test/ArchiveTests.cs | 41 +--- .../SharpCompress.Test/Rar/RarArchiveTests.cs | 77 ++++++- .../SevenZip/SevenZipArchiveTests.cs | 16 +- .../SharpCompress.Test/Zip/ZipArchiveTests.cs | 55 ++++- 18 files changed, 752 insertions(+), 552 deletions(-) create mode 100644 src/SharpCompress/Archives/ArchiveVolumeFactory.cs create mode 100644 src/SharpCompress/Archives/Zip/ZipArchiveVolumeFactory.cs create mode 100644 src/SharpCompress/IO/SourceStream.cs delete mode 100644 src/SharpCompress/IO/SplitStream.cs diff --git a/src/SharpCompress/Archives/AbstractArchive.cs b/src/SharpCompress/Archives/AbstractArchive.cs index 38e47ade..b6c0ef76 100644 --- a/src/SharpCompress/Archives/AbstractArchive.cs +++ b/src/SharpCompress/Archives/AbstractArchive.cs @@ -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(LoadVolumes(fileInfo)); - lazyEntries = new LazyReadOnlyCollection(LoadEntries(Volumes)); - } - - - protected abstract IEnumerable LoadVolumes(FileInfo file); - - internal AbstractArchive(ArchiveType type, IEnumerable streams, ReaderOptions readerOptions) - { - Type = type; - ReaderOptions = readerOptions; - lazyVolumes = new LazyReadOnlyCollection(LoadVolumes(streams.Select(CheckStreams))); + ReaderOptions = srcStream.ReaderOptions; + SrcStream = srcStream; + lazyVolumes = new LazyReadOnlyCollection(LoadVolumes(SrcStream)); lazyEntries = new LazyReadOnlyCollection(LoadEntries(Volumes)); } @@ -98,7 +85,7 @@ namespace SharpCompress.Archives /// public virtual long TotalUncompressSize => Entries.Aggregate(0L, (total, cf) => total + cf.Size); - protected abstract IEnumerable LoadVolumes(IEnumerable streams); + protected abstract IEnumerable LoadVolumes(SourceStream srcStream); protected abstract IEnumerable LoadEntries(IEnumerable volumes); IEnumerable IArchive.Entries => Entries.Cast(); @@ -111,6 +98,8 @@ namespace SharpCompress.Archives { lazyVolumes.ForEach(v => v.Dispose()); lazyEntries.GetLoaded().Cast().ForEach(x => x.Close()); + if (SrcStream != null) + SrcStream.Dispose(); disposed = true; } } diff --git a/src/SharpCompress/Archives/AbstractWritableArchive.cs b/src/SharpCompress/Archives/AbstractWritableArchive.cs index 7814170b..42a8213e 100644 --- a/src/SharpCompress/Archives/AbstractWritableArchive.cs +++ b/src/SharpCompress/Archives/AbstractWritableArchive.cs @@ -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 streams, ReaderOptions readerFactoryOptions) - : base(type, streams, readerFactoryOptions) - { - } - - public override ICollection Entries { get diff --git a/src/SharpCompress/Archives/ArchiveFactory.cs b/src/SharpCompress/Archives/ArchiveFactory.cs index c280f7f4..76c0f642 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.cs @@ -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"); + } + + /// + /// Constructor with IEnumerable FileInfo objects, multi and split support. + /// + /// + /// + public static IArchive Open(IEnumerable 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; } + + /// + /// From a passed in archive (zip, rar, 7z, 001), return all parts. + /// + /// + /// + public static IEnumerable GetFileParts(string part1) + { + part1.CheckNotNullOrEmpty(nameof(part1)); + return GetFileParts(new FileInfo(part1)).Select(a => a.FullName); + } + + /// + /// From a passed in archive (zip, rar, 7z, 001), return all parts. + /// + /// + /// + public static IEnumerable 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; + } + } } } diff --git a/src/SharpCompress/Archives/ArchiveVolumeFactory.cs b/src/SharpCompress/Archives/ArchiveVolumeFactory.cs new file mode 100644 index 00000000..09315b40 --- /dev/null +++ b/src/SharpCompress/Archives/ArchiveVolumeFactory.cs @@ -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; + } + + } +} diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.cs b/src/SharpCompress/Archives/GZip/GZipArchive.cs index 8360aad9..e24f6b78 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchive.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchive.cs @@ -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())); + } + + /// + /// Constructor with all file parts passed in + /// + /// + /// + public static GZipArchive Open(IEnumerable 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())); + } + + /// + /// Constructor with all stream parts passed in + /// + /// + /// + public static GZipArchive Open(IEnumerable 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())); } /// @@ -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 } /// - /// Constructor with a FileInfo object to an existing file. + /// Constructor with a SourceStream able to handle FileInfo and Streams. /// - /// + /// /// - internal GZipArchive(FileInfo fileInfo, ReaderOptions options) - : base(ArchiveType.GZip, fileInfo, options) + internal GZipArchive(SourceStream srcStream) + : base(ArchiveType.Tar, srcStream) { } - protected override IEnumerable LoadVolumes(FileInfo file) + protected override IEnumerable 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; } - /// - /// Takes multiple seekable Streams for a multi-part archive - /// - /// - /// - 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 LoadVolumes(IEnumerable streams) - { - return new GZipVolume(streams.First(), ReaderOptions).AsEnumerable(); - } - protected override IEnumerable LoadEntries(IEnumerable volumes) { Stream stream = volumes.Single().Stream; diff --git a/src/SharpCompress/Archives/Rar/RarArchive.cs b/src/SharpCompress/Archives/Rar/RarArchive.cs index c7b0934c..a5d3cc4d 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.cs @@ -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 UnpackV2017 { get; } = new Lazy(() => new SharpCompress.Compressors.Rar.UnpackV2017.Unpack()); internal Lazy UnpackV1 { get; } = new Lazy(() => new SharpCompress.Compressors.Rar.UnpackV1.Unpack()); - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - internal RarArchive(FileInfo fileInfo, ReaderOptions options) - : base(ArchiveType.Rar, fileInfo, options) - { - } - - protected override IEnumerable LoadVolumes(FileInfo file) - { - return RarArchiveVolumeFactory.GetParts(file, ReaderOptions); - } /// - /// Takes multiple seekable Streams for a multi-part archive + /// Constructor with a SourceStream able to handle FileInfo and Streams. /// - /// + /// /// - internal RarArchive(IEnumerable 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 LoadVolumes(IEnumerable streams) + protected override IEnumerable 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())); } /// @@ -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())); } /// @@ -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())); } /// - /// Takes multiple seekable Streams for a multi-part archive + /// Constructor with all file parts passed in + /// + /// + /// + public static RarArchive Open(IEnumerable 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())); + } + + /// + /// Constructor with all stream parts passed in /// /// - /// - public static RarArchive Open(IEnumerable streams, ReaderOptions? options = null) + /// + public static RarArchive Open(IEnumerable 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)); diff --git a/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs b/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs index 8740961b..abbd73b3 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs @@ -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 GetParts(IEnumerable 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 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); - } } } diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs index 62d31eac..eeae90b2 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -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())); } + + /// + /// Constructor with all file parts passed in + /// + /// + /// + public static SevenZipArchive Open(IEnumerable 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())); + } + + /// + /// Constructor with all stream parts passed in + /// + /// + /// + public static SevenZipArchive Open(IEnumerable 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())); + } + /// /// Takes a seekable Stream as a source /// @@ -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) + /// + /// Constructor with a SourceStream able to handle FileInfo and Streams. + /// + /// + /// + internal SevenZipArchive(SourceStream srcStream) + : base(ArchiveType.SevenZip, srcStream) { } - protected override IEnumerable LoadVolumes(FileInfo file) + protected override IEnumerable 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 LoadVolumes(IEnumerable 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 LoadEntries(IEnumerable volumes) { var stream = volumes.Single().Stream; diff --git a/src/SharpCompress/Archives/Tar/TarArchive.cs b/src/SharpCompress/Archives/Tar/TarArchive.cs index 73607f29..236fccba 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.cs @@ -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())); + } + + /// + /// Constructor with all file parts passed in + /// + /// + /// + public static TarArchive Open(IEnumerable 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())); + } + + /// + /// Constructor with all stream parts passed in + /// + /// + /// + public static TarArchive Open(IEnumerable 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())); } /// @@ -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; } - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - internal TarArchive(FileInfo fileInfo, ReaderOptions readerOptions) - : base(ArchiveType.Tar, fileInfo, readerOptions) + protected override IEnumerable LoadVolumes(SourceStream srcStream) { - } - - protected override IEnumerable 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 } /// - /// Takes multiple seekable Streams for a multi-part archive + /// Constructor with a SourceStream able to handle FileInfo and Streams. /// - /// - /// - internal TarArchive(Stream stream, ReaderOptions readerOptions) - : base(ArchiveType.Tar, stream, readerOptions) + /// + /// + internal TarArchive(SourceStream srcStream) + : base(ArchiveType.Tar, srcStream) { } @@ -110,11 +125,6 @@ namespace SharpCompress.Archives.Tar { } - protected override IEnumerable LoadVolumes(IEnumerable streams) - { - return new TarVolume(streams.First(), ReaderOptions).AsEnumerable(); - } - protected override IEnumerable LoadEntries(IEnumerable volumes) { Stream stream = volumes.Single().Stream; diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.cs b/src/SharpCompress/Archives/Zip/ZipArchive.cs index 4a5abb74..168d5838 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchive.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchive.cs @@ -26,6 +26,17 @@ namespace SharpCompress.Archives.Zip /// public CompressionLevel DeflateCompressionLevel { get; set; } + /// + /// Constructor with a SourceStream able to handle FileInfo and Streams. + /// + /// + /// + internal ZipArchive(SourceStream srcStream) + : base(ArchiveType.Zip, srcStream) + { + headerFactory = new SeekableZipHeaderFactory(srcStream.ReaderOptions.Password, srcStream.ReaderOptions.ArchiveEncoding); + } + /// /// Constructor expects a filepath to an existing file. /// @@ -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())); } /// - /// Takes multiple seekable Streams for a multi-part archive + /// Constructor with all file parts passed in + /// + /// + /// + public static ZipArchive Open(IEnumerable 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())); + } + + /// + /// Constructor with all stream parts passed in /// /// - /// - public static ZipArchive Open(IEnumerable streams, ReaderOptions? options = null) + /// + public static ZipArchive Open(IEnumerable 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())); } - /// - /// Takes multiple seekable Streams for a multi-part archive - /// - /// - /// - internal ZipArchive(IEnumerable streams, ReaderOptions options) - : base(ArchiveType.Zip, streams, options) - { - headerFactory = new SeekableZipHeaderFactory(options.Password, options.ArchiveEncoding); - } - - /// /// Takes a seekable Stream as a source /// @@ -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 } } - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - internal ZipArchive(FileInfo fileInfo, ReaderOptions readerOptions) - : base(ArchiveType.Zip, fileInfo, readerOptions) + protected override IEnumerable LoadVolumes(SourceStream srcStream) { - headerFactory = new SeekableZipHeaderFactory(readerOptions.Password, readerOptions.ArchiveEncoding); - } + base.SrcStream.LoadAllParts(); //request all streams + base.SrcStream.Position = 0; - protected override IEnumerable LoadVolumes(FileInfo file) - { - return new ZipVolume(file.OpenRead(), ReaderOptions).AsEnumerable(); + List 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 { } - /// - /// Takes multiple seekable Streams for a multi-part archive - /// - /// - /// - internal ZipArchive(Stream stream, ReaderOptions readerOptions) - : base(ArchiveType.Zip, stream, readerOptions) - { - headerFactory = new SeekableZipHeaderFactory(readerOptions.Password, readerOptions.ArchiveEncoding); - } - - protected override IEnumerable LoadVolumes(IEnumerable 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 LoadEntries(IEnumerable 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)); diff --git a/src/SharpCompress/Archives/Zip/ZipArchiveVolumeFactory.cs b/src/SharpCompress/Archives/Zip/ZipArchiveVolumeFactory.cs new file mode 100644 index 00000000..439e5a70 --- /dev/null +++ b/src/SharpCompress/Archives/Zip/ZipArchiveVolumeFactory.cs @@ -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 + } + + } +} diff --git a/src/SharpCompress/IO/SourceStream.cs b/src/SharpCompress/IO/SourceStream.cs new file mode 100644 index 00000000..c13c0269 --- /dev/null +++ b/src/SharpCompress/IO/SourceStream.cs @@ -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 _files; + private List _streams; + private Func _getFilePart; + private Func _getStreamPart; + private int _stream; + + public SourceStream(FileInfo file, Func getPart, ReaderOptions options) : this(null, null, file, getPart, options) + { + } + + public SourceStream(Stream stream, Func getPart, ReaderOptions options) : this(stream, getPart, null, null, options) + { + } + + private SourceStream(Stream? stream, Func? getStreamPart, FileInfo? file, Func? getFilePart, ReaderOptions options) + { + this.ReaderOptions = options; + _files = new List(); + _streams = new List(); + IsFileMode = file != null; + IsVolumes = false; + + if (!IsFileMode) + { + _streams.Add(stream!); + _getStreamPart = getStreamPart!; + _getFilePart = new Func(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(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 Files => _files; + public IEnumerable 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); + } + } +} diff --git a/src/SharpCompress/IO/SplitStream.cs b/src/SharpCompress/IO/SplitStream.cs deleted file mode 100644 index 087f8c92..00000000 --- a/src/SharpCompress/IO/SplitStream.cs +++ /dev/null @@ -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 files) : this(null, files) - { - } - - public SplitStream(IEnumerable streams) : this(streams, null) - { - } - - private SplitStream(IEnumerable? streams, IEnumerable? 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); - } - } -} diff --git a/src/SharpCompress/SharpCompress.csproj b/src/SharpCompress/SharpCompress.csproj index b5087f95..47230421 100644 --- a/src/SharpCompress/SharpCompress.csproj +++ b/src/SharpCompress/SharpCompress.csproj @@ -26,6 +26,8 @@ true snupkg true + False + False diff --git a/tests/SharpCompress.Test/ArchiveTests.cs b/tests/SharpCompress.Test/ArchiveTests.cs index 1702da14..0bf586e9 100644 --- a/tests/SharpCompress.Test/ArchiveTests.cs +++ b/tests/SharpCompress.Test/ArchiveTests.cs @@ -12,6 +12,15 @@ namespace SharpCompress.Test { public class ArchiveTests : ReaderTests { + protected void ArchiveGetParts(IEnumerable 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 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))); diff --git a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs index 87b71c61..75bec5cf 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs @@ -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] diff --git a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs index 93992861..c0e8a391 100644 --- a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs +++ b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs @@ -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" + } + + } } diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs index b6199012..0fc9d168 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs @@ -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",