diff --git a/src/SharpCompress/Archives/ArchiveFactory.cs b/src/SharpCompress/Archives/ArchiveFactory.cs index 76c0f642..8bb8f3f3 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.cs @@ -15,6 +15,36 @@ namespace SharpCompress.Archives { public static class ArchiveFactory { + private static readonly HashSet archiveFactories; + + /// + /// Gets the collection of registered archive factories + /// + public static IReadOnlyCollection Factories => archiveFactories; + + static ArchiveFactory() + { + archiveFactories = new HashSet(); + + RegisterFactory(new Zip.ZipArchiveFactory()); + RegisterFactory(new Rar.RarArchiveFactory()); + RegisterFactory(new SevenZip.SevenZipArchiveFactory()); + RegisterFactory(new GZip.GZipArchiveFactory()); + RegisterFactory(new Tar.TarArchiveFactory()); + } + + /// + /// Registers an archive factory. + /// + /// The factory to register. + /// must not be null. + public static void RegisterFactory(IArchiveFactory factory) + { + factory.CheckNotNull(nameof(factory)); + + archiveFactories.Add(factory); + } + /// /// Opens an Archive for random access /// @@ -28,28 +58,10 @@ namespace SharpCompress.Archives { throw new ArgumentException("Stream should be readable and seekable"); } + readerOptions ??= new ReaderOptions(); - ArchiveType? type; - IsArchive(stream, out type); //test and reset stream position - - if (type != null) - { - switch (type.Value) - { - case ArchiveType.Zip: - return ZipArchive.Open(stream, readerOptions); - case ArchiveType.SevenZip: - return SevenZipArchive.Open(stream, readerOptions); - case ArchiveType.GZip: - return GZipArchive.Open(stream, readerOptions); - case ArchiveType.Rar: - return RarArchive.Open(stream, readerOptions); - case ArchiveType.Tar: - return TarArchive.Open(stream, readerOptions); - } - } - throw new InvalidOperationException("Cannot determine compressed stream type. Supported Archive Formats: Zip, GZip, Tar, Rar, 7Zip, LZip"); + return FindArchiveFactory(stream).Open(stream, readerOptions); } public static IWritableArchive Create(ArchiveType type) @@ -84,29 +96,7 @@ namespace SharpCompress.Archives fileInfo.CheckNotNull(nameof(fileInfo)); options ??= new ReaderOptions { LeaveStreamOpen = false }; - ArchiveType? type; - using (Stream stream = fileInfo.OpenRead()) - { - IsArchive(stream, out type); //test and reset stream position - - if (type != null) - { - switch (type.Value) - { - case ArchiveType.Zip: - return ZipArchive.Open(fileInfo, options); - case ArchiveType.SevenZip: - return SevenZipArchive.Open(fileInfo, options); - case ArchiveType.GZip: - return GZipArchive.Open(fileInfo, options); - case ArchiveType.Rar: - return RarArchive.Open(fileInfo, options); - case ArchiveType.Tar: - return TarArchive.Open(fileInfo, options); - } - } - } - throw new InvalidOperationException("Cannot determine compressed stream type. Supported Archive Formats: Zip, GZip, Tar, Rar, 7Zip"); + return FindArchiveFactory(fileInfo).Open(fileInfo, options); } /// @@ -124,31 +114,10 @@ namespace SharpCompress.Archives if (files.Length == 1) return Open(fileInfo, options); - fileInfo.CheckNotNull(nameof(fileInfo)); options ??= new ReaderOptions { LeaveStreamOpen = false }; - ArchiveType? type; - using (Stream stream = fileInfo.OpenRead()) - IsArchive(stream, out type); //test and reset stream position - - if (type != null) - { - switch (type.Value) - { - case ArchiveType.Zip: - return ZipArchive.Open(files, options); - case ArchiveType.SevenZip: - return SevenZipArchive.Open(files, options); - case ArchiveType.GZip: - return GZipArchive.Open(files, options); - case ArchiveType.Rar: - return RarArchive.Open(files, options); - case ArchiveType.Tar: - return TarArchive.Open(files, options); - } - } - throw new InvalidOperationException("Cannot determine compressed stream type. Supported Archive Formats: Zip, GZip, Tar, Rar, 7Zip"); + return FindArchiveFactory(fileInfo).Open(fileInfos, options); } /// @@ -159,36 +128,18 @@ namespace SharpCompress.Archives public static IArchive Open(IEnumerable streams, ReaderOptions? options = null) { streams.CheckNotNull(nameof(streams)); - if (streams.Count() == 0) + Stream[] streamsArray = streams.ToArray(); + if (streamsArray.Length == 0) throw new InvalidOperationException("No streams"); - if (streams.Count() == 1) - return Open(streams.First(), options); - + Stream firstStream = streamsArray[0]; + if (streamsArray.Length == 1) + return Open(firstStream, options); + firstStream.CheckNotNull(nameof(firstStream)); 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"); - } + return FindArchiveFactory(firstStream).Open(streamsArray, options); + } /// /// Extract to specific directory, retaining filename @@ -203,6 +154,44 @@ namespace SharpCompress.Archives } } + private static IArchiveFactory FindArchiveFactory(FileInfo finfo) + { + finfo.CheckNotNull(nameof(finfo)); + using (Stream stream = finfo.OpenRead()) + { + return FindArchiveFactory(stream); + } + } + + private static IArchiveFactory FindArchiveFactory(Stream stream) + { + stream.CheckNotNull(nameof(stream)); + if (!stream.CanRead || !stream.CanSeek) + { + throw new ArgumentException("Stream should be readable and seekable"); + } + + long startPosition = stream.Position; + + foreach (var factory in Factories) + { + stream.Seek(startPosition, SeekOrigin.Begin); + + if (factory.IsArchive(stream)) + { + stream.Seek(startPosition, SeekOrigin.Begin); + + return factory; + } + } + + var extensions = Factories + .Select(item => item.Name) + .Aggregate((a, b) => a + ", " + b); + + throw new InvalidOperationException($"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"); + } + public static bool IsArchive(string filePath, out ArchiveType? type) { filePath.CheckNotNullOrEmpty(nameof(filePath)); diff --git a/src/SharpCompress/Archives/GZip/GZipArchiveFactory.cs b/src/SharpCompress/Archives/GZip/GZipArchiveFactory.cs new file mode 100644 index 00000000..0d52d6ca --- /dev/null +++ b/src/SharpCompress/Archives/GZip/GZipArchiveFactory.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using SharpCompress.Readers; + +namespace SharpCompress.Archives.GZip +{ + public class GZipArchiveFactory : IArchiveFactory + { + /// + public string Name => "GZip"; + + /// + public IEnumerable GetSupportedExtensions() + { + yield return "gz"; + } + + /// + public bool IsArchive(Stream stream, string? password = null) + { + return GZipArchive.IsGZipFile(stream); + } + + /// + public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) + { + return GZipArchive.Open(stream, readerOptions); + } + + /// + public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + return GZipArchive.Open(fileInfo, readerOptions); + } + + /// + public IArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) + { + return GZipArchive.Open(streams, readerOptions); + } + + /// + public IArchive Open(IEnumerable fileInfos, ReaderOptions? readerOptions = null) + { + return GZipArchive.Open(fileInfos, readerOptions); + } + } +} diff --git a/src/SharpCompress/Archives/IArchiveFactory.cs b/src/SharpCompress/Archives/IArchiveFactory.cs new file mode 100644 index 00000000..5674307d --- /dev/null +++ b/src/SharpCompress/Archives/IArchiveFactory.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using SharpCompress.Common; +using SharpCompress.Readers; + +namespace SharpCompress.Archives +{ + /// + /// Represents a factory used to identify and open archives. + /// + /// + /// Currently implemented by:
+ /// + /// + /// + /// + /// + /// + /// + ///
+ public interface IArchiveFactory + { + /// + /// Gets the archive Type name + /// + string Name { get; } + + /// + /// returns the extensions typically used by this archive type. + /// + /// + IEnumerable GetSupportedExtensions(); + + /// + /// Returns true if the stream represents an archive of the format defined by this type. + /// + /// + /// /// optional password + bool IsArchive(Stream stream, string? password = null); + + /// + /// Opens an Archive for random access. + /// + /// An open, readable and seekable stream. + /// reading options. + IArchive Open(Stream stream, ReaderOptions? readerOptions = null); + + /// + /// Constructor with a FileInfo object to an existing file. + /// + /// the file to open. + /// reading options. + IArchive Open(System.IO.FileInfo fileInfo, ReaderOptions? readerOptions = null); + + /// + /// Constructor with IEnumerable FileInfo objects, multi and split support. + /// + /// + /// reading options. + IArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null); + + /// + /// Constructor with IEnumerable Stream objects, multi and split support. + /// + /// + /// reading options. + IArchive Open(IEnumerable fileInfos, ReaderOptions? readerOptions = null); + } +} diff --git a/src/SharpCompress/Archives/Rar/RarArchiveFactory.cs b/src/SharpCompress/Archives/Rar/RarArchiveFactory.cs new file mode 100644 index 00000000..a049275c --- /dev/null +++ b/src/SharpCompress/Archives/Rar/RarArchiveFactory.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using SharpCompress.Readers; + +namespace SharpCompress.Archives.Rar +{ + public class RarArchiveFactory : IArchiveFactory + { + /// + public string Name => "Rar"; + + /// + public IEnumerable GetSupportedExtensions() + { + yield return "rar"; + yield return "cbr"; + } + + /// + public bool IsArchive(Stream stream, string? password = null) + { + return RarArchive.IsRarFile(stream); + } + + /// + public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) + { + return RarArchive.Open(stream, readerOptions); + } + + /// + public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + return RarArchive.Open(fileInfo, readerOptions); + } + + /// + public IArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) + { + return RarArchive.Open(streams, readerOptions); + } + + /// + public IArchive Open(IEnumerable fileInfos, ReaderOptions? readerOptions = null) + { + return RarArchive.Open(fileInfos, readerOptions); + } + } +} diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchiveFactory.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchiveFactory.cs new file mode 100644 index 00000000..153fb431 --- /dev/null +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchiveFactory.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using SharpCompress.Readers; + +namespace SharpCompress.Archives.SevenZip +{ + public class SevenZipArchiveFactory : IArchiveFactory + { + /// + public string Name => "7Zip"; + + /// + public IEnumerable GetSupportedExtensions() + { + yield return "7z"; + } + + /// + public bool IsArchive(Stream stream, string? password = null) + { + return SevenZipArchive.IsSevenZipFile(stream); + } + + /// + public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) + { + return SevenZipArchive.Open(stream, readerOptions); + } + + /// + public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + return SevenZipArchive.Open(fileInfo, readerOptions); + } + + /// + public IArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) + { + return SevenZipArchive.Open(streams, readerOptions); + } + + /// + public IArchive Open(IEnumerable fileInfos, ReaderOptions? readerOptions = null) + { + return SevenZipArchive.Open(fileInfos, readerOptions); + } + } +} diff --git a/src/SharpCompress/Archives/Tar/TarArchiveFactory.cs b/src/SharpCompress/Archives/Tar/TarArchiveFactory.cs new file mode 100644 index 00000000..fb9c170c --- /dev/null +++ b/src/SharpCompress/Archives/Tar/TarArchiveFactory.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using SharpCompress.Readers; + +namespace SharpCompress.Archives.Tar +{ + public class TarArchiveFactory : IArchiveFactory + { + /// + public string Name => "Tar"; + + /// + public IEnumerable GetSupportedExtensions() + { + yield return "tar"; + } + + /// + public bool IsArchive(Stream stream, string? password = null) + { + return TarArchive.IsTarFile(stream); + } + + /// + public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) + { + return TarArchive.Open(stream, readerOptions); + } + + /// + public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + return TarArchive.Open(fileInfo, readerOptions); + } + + /// + public IArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) + { + return TarArchive.Open(streams, readerOptions); + } + + /// + public IArchive Open(IEnumerable fileInfos, ReaderOptions? readerOptions = null) + { + return TarArchive.Open(fileInfos, readerOptions); + } + + } +} diff --git a/src/SharpCompress/Archives/Zip/ZipArchiveFactory.cs b/src/SharpCompress/Archives/Zip/ZipArchiveFactory.cs new file mode 100644 index 00000000..68c3f2b6 --- /dev/null +++ b/src/SharpCompress/Archives/Zip/ZipArchiveFactory.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using SharpCompress.Readers; + +namespace SharpCompress.Archives.Zip +{ + public class ZipArchiveFactory : IArchiveFactory + { + /// + public string Name => "Zip"; + + /// + public IEnumerable GetSupportedExtensions() + { + yield return "zip"; + yield return "zipx"; + yield return "cbz"; + } + + /// + public bool IsArchive(Stream stream, string? password = null) + { + long startPosition = stream.Position; + + if (ZipArchive.IsZipFile(stream, password)) return true; + + stream.Seek(startPosition, SeekOrigin.Begin); + + //test the zip (last) file of a multipart zip + if (ZipArchive.IsZipMulti(stream)) return true; + + return false; + } + + /// + public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) + { + return ZipArchive.Open(stream, readerOptions); + } + + /// + public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + return ZipArchive.Open(fileInfo, readerOptions); + } + + /// + public IArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) + { + return ZipArchive.Open(streams, readerOptions); + } + + /// + public IArchive Open(IEnumerable fileInfos, ReaderOptions? readerOptions = null) + { + return ZipArchive.Open(fileInfos, readerOptions); + } + } +}