diff --git a/src/SharpCompress/Archives/ArchiveFactory.cs b/src/SharpCompress/Archives/ArchiveFactory.cs index 8bb8f3f3..71c0358e 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.cs @@ -8,6 +8,7 @@ using SharpCompress.Archives.SevenZip; using SharpCompress.Archives.Tar; using SharpCompress.Archives.Zip; using SharpCompress.Common; +using SharpCompress.Factories; using SharpCompress.IO; using SharpCompress.Readers; @@ -15,36 +16,6 @@ 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 /// @@ -53,26 +24,23 @@ namespace SharpCompress.Archives /// public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null) { - stream.CheckNotNull(nameof(stream)); - if (!stream.CanRead || !stream.CanSeek) - { - throw new ArgumentException("Stream should be readable and seekable"); - } - readerOptions ??= new ReaderOptions(); - return FindArchiveFactory(stream).Open(stream, readerOptions); + return FindFactory(stream).Open(stream, readerOptions); } public static IWritableArchive Create(ArchiveType type) { - return type switch - { - ArchiveType.Zip => ZipArchive.Create(), - ArchiveType.Tar => TarArchive.Create(), - ArchiveType.GZip => GZipArchive.Create(), - _ => throw new NotSupportedException("Cannot create Archives of type: " + type) - }; + var factory = Factories.Factory + .Factories + .OfType() + .Where(item => item.KnownArchiveType == type) + .FirstOrDefault(); + + if (factory != null) + return factory.CreateWriteableArchive(); + + throw new NotSupportedException("Cannot create Archives of type: " + type); } /// @@ -92,11 +60,10 @@ namespace SharpCompress.Archives /// /// public static IArchive Open(FileInfo fileInfo, ReaderOptions? options = null) - { - fileInfo.CheckNotNull(nameof(fileInfo)); + { options ??= new ReaderOptions { LeaveStreamOpen = false }; - return FindArchiveFactory(fileInfo).Open(fileInfo, options); + return FindFactory(fileInfo).Open(fileInfo, options); } /// @@ -107,17 +74,17 @@ namespace SharpCompress.Archives public static IArchive Open(IEnumerable fileInfos, ReaderOptions? options = null) { fileInfos.CheckNotNull(nameof(fileInfos)); - FileInfo[] files = fileInfos.ToArray(); - if (files.Length == 0) + FileInfo[] filesArray = fileInfos.ToArray(); + if (filesArray.Length == 0) throw new InvalidOperationException("No files to open"); - FileInfo fileInfo = files[0]; - if (files.Length == 1) + FileInfo fileInfo = filesArray[0]; + if (filesArray.Length == 1) return Open(fileInfo, options); fileInfo.CheckNotNull(nameof(fileInfo)); options ??= new ReaderOptions { LeaveStreamOpen = false }; - return FindArchiveFactory(fileInfo).Open(fileInfos, options); + return FindFactory(fileInfo).Open(filesArray, options); } /// @@ -138,7 +105,7 @@ namespace SharpCompress.Archives firstStream.CheckNotNull(nameof(firstStream)); options ??= new ReaderOptions(); - return FindArchiveFactory(firstStream).Open(streamsArray, options); + return FindFactory(firstStream).Open(streamsArray, options); } /// @@ -154,16 +121,18 @@ namespace SharpCompress.Archives } } - private static IArchiveFactory FindArchiveFactory(FileInfo finfo) + private static T FindFactory(FileInfo finfo) + where T : IFactory { finfo.CheckNotNull(nameof(finfo)); using (Stream stream = finfo.OpenRead()) { - return FindArchiveFactory(stream); + return FindFactory(stream); } } - private static IArchiveFactory FindArchiveFactory(Stream stream) + private static T FindFactory(Stream stream) + where T: IFactory { stream.CheckNotNull(nameof(stream)); if (!stream.CanRead || !stream.CanSeek) @@ -171,9 +140,11 @@ namespace SharpCompress.Archives throw new ArgumentException("Stream should be readable and seekable"); } + var factories = Factories.Factory.Factories.OfType(); + long startPosition = stream.Position; - foreach (var factory in Factories) + foreach (var factory in factories) { stream.Seek(startPosition, SeekOrigin.Begin); @@ -185,9 +156,7 @@ namespace SharpCompress.Archives } } - var extensions = Factories - .Select(item => item.Name) - .Aggregate((a, b) => a + ", " + b); + var extensions = string.Join(", ", factories.Select(item => item.Name)); throw new InvalidOperationException($"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"); } @@ -208,41 +177,21 @@ namespace SharpCompress.Archives { throw new ArgumentException("Stream should be readable and seekable"); } - if (ZipArchive.IsZipFile(stream, null)) - type = ArchiveType.Zip; - stream.Seek(0, SeekOrigin.Begin); - if (type == null) + + var startPosition = stream.Position; + + foreach(var factory in Factories.Factory.Factories) { - if (SevenZipArchive.IsSevenZipFile(stream)) - type = ArchiveType.SevenZip; - stream.Seek(0, SeekOrigin.Begin); - } - if (type == null) - { - if (GZipArchive.IsGZipFile(stream)) - type = ArchiveType.GZip; - stream.Seek(0, SeekOrigin.Begin); - } - if (type == null) - { - if (RarArchive.IsRarFile(stream)) - type = ArchiveType.Rar; - stream.Seek(0, SeekOrigin.Begin); - } - if (type == null) - { - if (TarArchive.IsTarFile(stream)) - type = ArchiveType.Tar; - stream.Seek(0, SeekOrigin.Begin); - } - if (type == null) //test multipartzip as it could find zips in other non compressed archive types? - { - if (ZipArchive.IsZipMulti(stream)) //test the zip (last) file of a multipart zip - type = ArchiveType.Zip; - stream.Seek(0, SeekOrigin.Begin); + stream.Position = startPosition; + + if (factory.IsArchive(stream, null)) + { + type = factory.KnownArchiveType; + return true; + } } - return type != null; + return false; } /// @@ -264,21 +213,21 @@ namespace SharpCompress.Archives public static IEnumerable GetFileParts(FileInfo part1) { part1.CheckNotNull(nameof(part1)); - yield return part1; - int i = 1; + yield return part1; - FileInfo? part = RarArchiveVolumeFactory.GetFilePart(i++, part1); - if (part != null) + foreach(var factory in Factory.Factories.OfType()) { - 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 + int i = 1; + FileInfo? part = factory.GetFilePart(i++, part1); + + if (part != null) + { yield return part; + while ((part = factory.GetFilePart(i++, part1)) != null) //tests split too + yield return part; + + yield break; + } } } } diff --git a/src/SharpCompress/Archives/GZip/GZipArchiveFactory.cs b/src/SharpCompress/Archives/GZip/GZipArchiveFactory.cs deleted file mode 100644 index 0d52d6ca..00000000 --- a/src/SharpCompress/Archives/GZip/GZipArchiveFactory.cs +++ /dev/null @@ -1,51 +0,0 @@ -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 index 5674307d..98014688 100644 --- a/src/SharpCompress/Archives/IArchiveFactory.cs +++ b/src/SharpCompress/Archives/IArchiveFactory.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using SharpCompress.Common; +using SharpCompress.Factories; using SharpCompress.Readers; namespace SharpCompress.Archives @@ -13,33 +14,15 @@ namespace SharpCompress.Archives /// /// Currently implemented by:
/// - /// - /// - /// - /// - /// + /// + /// + /// + /// + /// /// ///
- public interface IArchiveFactory + public interface IArchiveFactory : IFactory { - /// - /// 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. /// @@ -53,19 +36,5 @@ namespace SharpCompress.Archives /// 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/IMultiArchiveFactory.cs b/src/SharpCompress/Archives/IMultiArchiveFactory.cs new file mode 100644 index 00000000..6ba39280 --- /dev/null +++ b/src/SharpCompress/Archives/IMultiArchiveFactory.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using SharpCompress.Common; +using SharpCompress.Factories; +using SharpCompress.Readers; + +namespace SharpCompress.Archives +{ + /// + /// Represents a factory used to identify and open archives. + /// + /// + /// Implemented by:
+ /// + /// + /// + /// + /// + /// + /// + ///
+ public interface IMultiArchiveFactory : IFactory + { + /// + /// Constructor with IEnumerable FileInfo objects, multi and split support. + /// + /// + /// reading options. + IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null); + + /// + /// Constructor with IEnumerable Stream objects, multi and split support. + /// + /// + /// reading options. + IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null); + } +} diff --git a/src/SharpCompress/Archives/IWriteableArchiveFactory.cs b/src/SharpCompress/Archives/IWriteableArchiveFactory.cs new file mode 100644 index 00000000..59c58134 --- /dev/null +++ b/src/SharpCompress/Archives/IWriteableArchiveFactory.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharpCompress.Archives +{ + /// + /// Decorator for used to declare an archive format as able to create writeable archives + /// + /// + /// Implemented by:
+ /// + /// + /// + /// + /// + public interface IWriteableArchiveFactory : Factories.IFactory + { + /// + /// Creates a new, empty archive, ready to be written. + /// + /// + IWritableArchive CreateWriteableArchive(); + } +} diff --git a/src/SharpCompress/Archives/Rar/RarArchiveFactory.cs b/src/SharpCompress/Archives/Rar/RarArchiveFactory.cs deleted file mode 100644 index a049275c..00000000 --- a/src/SharpCompress/Archives/Rar/RarArchiveFactory.cs +++ /dev/null @@ -1,52 +0,0 @@ -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 deleted file mode 100644 index 153fb431..00000000 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchiveFactory.cs +++ /dev/null @@ -1,51 +0,0 @@ -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 deleted file mode 100644 index fb9c170c..00000000 --- a/src/SharpCompress/Archives/Tar/TarArchiveFactory.cs +++ /dev/null @@ -1,52 +0,0 @@ -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 deleted file mode 100644 index 68c3f2b6..00000000 --- a/src/SharpCompress/Archives/Zip/ZipArchiveFactory.cs +++ /dev/null @@ -1,62 +0,0 @@ -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); - } - } -} diff --git a/src/SharpCompress/Factories/Factory.cs b/src/SharpCompress/Factories/Factory.cs new file mode 100644 index 00000000..9f5cb001 --- /dev/null +++ b/src/SharpCompress/Factories/Factory.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using SharpCompress.Archives; +using SharpCompress.Common; +using SharpCompress.IO; +using SharpCompress.Readers; + +namespace SharpCompress.Factories +{ + /// + public abstract class Factory : IFactory + { + static Factory() + { + RegisterFactory(new ZipFactory()); + RegisterFactory(new RarFactory()); + RegisterFactory(new SevenZipFactory()); + RegisterFactory(new GZipFactory()); + RegisterFactory(new TarFactory()); + } + + private static readonly HashSet _factories = new HashSet(); + + /// + /// Gets the collection of registered . + /// + public static IEnumerable Factories => _factories; + + /// + /// Registers an archive factory. + /// + /// The factory to register. + /// must not be null. + public static void RegisterFactory(Factory factory) + { + factory.CheckNotNull(nameof(factory)); + + _factories.Add(factory); + } + + /// + public abstract string Name { get; } + + /// + public virtual ArchiveType? KnownArchiveType => null; + + /// + public abstract IEnumerable GetSupportedExtensions(); + + /// + public abstract bool IsArchive(Stream stream, string? password = null); + + /// + public virtual FileInfo? GetFilePart(int index, FileInfo part1) + { + return null; + } + + /// + /// Tries to open an from a . + /// + /// + /// This method provides extra insight to support loading compressed TAR files. + /// + /// + /// + /// + /// + internal virtual bool TryOpenReader(RewindableStream rewindableStream, ReaderOptions options, out IReader? reader) + { + reader = null; + + if (this is IReaderFactory readerFactory) + { + rewindableStream.Rewind(false); + if (this.IsArchive(rewindableStream, options.Password)) + { + rewindableStream.Rewind(true); + reader = readerFactory.OpenReader(rewindableStream, options); + return true; + } + } + + return false; + } + } +} diff --git a/src/SharpCompress/Factories/GZipFactory.cs b/src/SharpCompress/Factories/GZipFactory.cs new file mode 100644 index 00000000..fe6f4260 --- /dev/null +++ b/src/SharpCompress/Factories/GZipFactory.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; + +using SharpCompress.Archives; +using SharpCompress.Archives.GZip; +using SharpCompress.Archives.Tar; +using SharpCompress.Common; +using SharpCompress.IO; +using SharpCompress.Readers; +using SharpCompress.Readers.GZip; +using SharpCompress.Readers.Tar; +using SharpCompress.Writers; +using SharpCompress.Writers.GZip; + +namespace SharpCompress.Factories +{ + /// + /// Represents the foundation factory of GZip archive. + /// + public class GZipFactory : Factory, + IArchiveFactory, + IMultiArchiveFactory, + IReaderFactory, + IWriterFactory, + IWriteableArchiveFactory + { + #region IFactory + + /// + public override string Name => "GZip"; + + /// + public override ArchiveType? KnownArchiveType => ArchiveType.GZip; + + /// + public override IEnumerable GetSupportedExtensions() + { + yield return "gz"; + } + + /// + public override bool IsArchive(Stream stream, string? password = null) + { + return GZipArchive.IsGZipFile(stream); + } + + #endregion + + #region IArchiveFactory + + /// + 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); + } + + #endregion + + #region IMultiArchiveFactory + + /// + public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) + { + return GZipArchive.Open(streams, readerOptions); + } + + /// + public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) + { + return GZipArchive.Open(fileInfos, readerOptions); + } + + #endregion + + #region IReaderFactory + + /// + internal override bool TryOpenReader(RewindableStream rewindableStream, ReaderOptions options, out IReader? reader) + { + reader = null; + + rewindableStream.Rewind(false); + if (GZipArchive.IsGZipFile(rewindableStream)) + { + rewindableStream.Rewind(false); + var testStream = new GZipStream(rewindableStream, CompressionMode.Decompress); + if (TarArchive.IsTarFile(testStream)) + { + rewindableStream.Rewind(true); + reader = new TarReader(rewindableStream, options, CompressionType.GZip); + return true; + } + + rewindableStream.Rewind(true); + reader = OpenReader(rewindableStream, options); + return true; + } + + return false; + } + + /// + public IReader OpenReader(Stream stream, ReaderOptions? options) + { + return GZipReader.Open(stream, options); + } + + #endregion + + #region IWriterFactory + + /// + public IWriter Open(Stream stream, WriterOptions writerOptions) + { + if (writerOptions.CompressionType != CompressionType.GZip) + { + throw new InvalidFormatException("GZip archives only support GZip compression type."); + } + return new GZipWriter(stream, new GZipWriterOptions(writerOptions)); + } + + #endregion + + #region IWriteableArchiveFactory + + /// + public IWritableArchive CreateWriteableArchive() + { + return GZipArchive.Create(); + } + + #endregion + } +} diff --git a/src/SharpCompress/Factories/IFactory.cs b/src/SharpCompress/Factories/IFactory.cs new file mode 100644 index 00000000..82e27f11 --- /dev/null +++ b/src/SharpCompress/Factories/IFactory.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharpCompress.Factories +{ + /// + /// Represents the foundation of an archive factory. + /// + /// + /// To get extended functionality, this type can be cast to:
+ ///
+ ///
+ ///
+ ///
+ public interface IFactory + { + /// + /// Gets the archive Type name + /// + string Name { get; } + + /// + /// Gets the archive Type in case it is a well known archive format. + /// + Common.ArchiveType? KnownArchiveType { 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. + /// + /// A stream, pointing to the beginning of the archive. + /// optional password + bool IsArchive(Stream stream, string? password = null); + + /// + /// From a passed in archive (zip, rar, 7z, 001), return all parts. + /// + /// Path to the first part. + /// + /// The path to the requested part, + /// or NULL if the part does not exist. + /// + FileInfo? GetFilePart(int index, FileInfo part1); + } +} + diff --git a/src/SharpCompress/Factories/RarFactory.cs b/src/SharpCompress/Factories/RarFactory.cs new file mode 100644 index 00000000..7d6c16ba --- /dev/null +++ b/src/SharpCompress/Factories/RarFactory.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using SharpCompress.Archives; +using SharpCompress.Archives.Rar; +using SharpCompress.Common; +using SharpCompress.IO; +using SharpCompress.Readers; +using SharpCompress.Readers.Rar; + +namespace SharpCompress.Factories +{ + /// + /// Represents the foundation factory of RAR archive. + /// + public class RarFactory : Factory, + IArchiveFactory, + IMultiArchiveFactory, + IReaderFactory + { + #region IArchive + + /// + public override string Name => "Rar"; + + /// + public override ArchiveType? KnownArchiveType => ArchiveType.Rar; + + /// + public override IEnumerable GetSupportedExtensions() + { + yield return "rar"; + yield return "cbr"; + } + + /// + public override bool IsArchive(Stream stream, string? password = null) + { + return RarArchive.IsRarFile(stream); + } + + /// + public override FileInfo? GetFilePart(int index, FileInfo part1) + { + return RarArchiveVolumeFactory.GetFilePart(index, part1); + } + + #endregion + + #region IArchiveFactory + + /// + 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); + } + + #endregion + + #region IMultiArchiveFactory + + /// + public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) + { + return RarArchive.Open(streams, readerOptions); + } + + /// + public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) + { + return RarArchive.Open(fileInfos, readerOptions); + } + + #endregion + + #region IReaderFactory + + /// + public IReader OpenReader(Stream stream, ReaderOptions? options) + { + return RarReader.Open(stream, options); + } + + #endregion + } +} diff --git a/src/SharpCompress/Factories/SevenZipFactory.cs b/src/SharpCompress/Factories/SevenZipFactory.cs new file mode 100644 index 00000000..c7d4dd8b --- /dev/null +++ b/src/SharpCompress/Factories/SevenZipFactory.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using SharpCompress.Archives; +using SharpCompress.Archives.SevenZip; +using SharpCompress.Common; +using SharpCompress.Common.SevenZip; +using SharpCompress.IO; +using SharpCompress.Readers; + +namespace SharpCompress.Factories +{ + /// + /// Represents the foundation factory of 7Zip archive. + /// + public class SevenZipFactory : Factory, + IArchiveFactory, + IMultiArchiveFactory + { + #region IFactory + + /// + public override string Name => "7Zip"; + + /// + public override ArchiveType? KnownArchiveType => ArchiveType.SevenZip; + + /// + public override IEnumerable GetSupportedExtensions() + { + yield return "7z"; + } + + /// + public override bool IsArchive(Stream stream, string? password = null) + { + return SevenZipArchive.IsSevenZipFile(stream); + } + + #endregion + + #region IArchiveFactory + + /// + 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); + } + + #endregion + + #region IMultiArchiveFactory + + /// + public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) + { + return SevenZipArchive.Open(streams, readerOptions); + } + + /// + public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) + { + return SevenZipArchive.Open(fileInfos, readerOptions); + } + + #endregion + + #region reader + + internal override bool TryOpenReader(RewindableStream rewindableStream, ReaderOptions options, out IReader? reader) + { + reader = null; + return false; + } + + #endregion + } +} diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs new file mode 100644 index 00000000..4214ca93 --- /dev/null +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using SharpCompress.Archives; +using SharpCompress.Archives.Tar; +using SharpCompress.Archives.Zip; +using SharpCompress.Common; +using SharpCompress.Compressors; +using SharpCompress.Compressors.BZip2; +using SharpCompress.Compressors.LZMA; +using SharpCompress.Compressors.Xz; +using SharpCompress.IO; +using SharpCompress.Readers; +using SharpCompress.Readers.Tar; +using SharpCompress.Writers; +using SharpCompress.Writers.Tar; + +namespace SharpCompress.Factories +{ + /// + /// Represents the foundation factory of TAR archive. + /// + public class TarFactory : Factory, + IArchiveFactory, + IMultiArchiveFactory, + IReaderFactory, + IWriterFactory, + IWriteableArchiveFactory + { + #region IFactory + + /// + public override string Name => "Tar"; + + /// + public override ArchiveType? KnownArchiveType => ArchiveType.Tar; + + /// + public override IEnumerable GetSupportedExtensions() + { + // from https://en.wikipedia.org/wiki/Tar_(computing)#Suffixes_for_compressed_files + + yield return "tar"; + + // gzip + yield return "taz"; + yield return "tgz"; + + // bzip2 + yield return "tb2"; + yield return "tbz"; + yield return "tbz2"; + yield return "tz2"; + + // lzma + // yield return "tlz"; // unsupported + + // xz + // yield return "txz"; // unsupported + + // compress + yield return "tZ"; + yield return "taZ"; + + // zstd + // yield return "tzst"; // unsupported + } + + /// + public override bool IsArchive(Stream stream, string? password = null) + { + return TarArchive.IsTarFile(stream); + } + + #endregion + + #region IArchiveFactory + + /// + 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); + } + + #endregion + + #region IMultiArchiveFactory + + /// + public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) + { + return TarArchive.Open(streams, readerOptions); + } + + /// + public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) + { + return TarArchive.Open(fileInfos, readerOptions); + } + + #endregion + + #region IReaderFactory + + /// + internal override bool TryOpenReader(RewindableStream rewindableStream, ReaderOptions options, out IReader? reader) + { + reader = null; + + rewindableStream.Rewind(false); + if (TarArchive.IsTarFile(rewindableStream)) + { + rewindableStream.Rewind(true); + reader = OpenReader(rewindableStream, options); + return true; + } + + rewindableStream.Rewind(false); + if (BZip2Stream.IsBZip2(rewindableStream)) + { + rewindableStream.Rewind(false); + var testStream = new BZip2Stream(NonDisposingStream.Create(rewindableStream), CompressionMode.Decompress, false); + if (TarArchive.IsTarFile(testStream)) + { + rewindableStream.Rewind(true); + reader = new TarReader(rewindableStream, options, CompressionType.BZip2); + return true; + } + } + + rewindableStream.Rewind(false); + if (LZipStream.IsLZipFile(rewindableStream)) + { + rewindableStream.Rewind(false); + var testStream = new LZipStream(NonDisposingStream.Create(rewindableStream), CompressionMode.Decompress); + if (TarArchive.IsTarFile(testStream)) + { + rewindableStream.Rewind(true); + reader = new TarReader(rewindableStream, options, CompressionType.LZip); + return true; + } + } + + rewindableStream.Rewind(false); + if (XZStream.IsXZStream(rewindableStream)) + { + rewindableStream.Rewind(true); + var testStream = new XZStream(rewindableStream); + if (TarArchive.IsTarFile(testStream)) + { + rewindableStream.Rewind(true); + reader = new TarReader(rewindableStream, options, CompressionType.Xz); + return true; + } + } + + return false; + } + + /// + public IReader OpenReader(Stream stream, ReaderOptions? options) + { + return TarReader.Open(stream, options); + } + + #endregion + + #region IWriterFactory + + /// + public IWriter Open(Stream stream, WriterOptions writerOptions) + { + return new TarWriter(stream, new TarWriterOptions(writerOptions)); + } + + #endregion + + #region IWriteableArchiveFactory + + /// + public IWritableArchive CreateWriteableArchive() + { + return TarArchive.Create(); + } + + #endregion + + } +} diff --git a/src/SharpCompress/Factories/ZipFactory.cs b/src/SharpCompress/Factories/ZipFactory.cs new file mode 100644 index 00000000..a3e6e757 --- /dev/null +++ b/src/SharpCompress/Factories/ZipFactory.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using SharpCompress.Archives; +using SharpCompress.Archives.Zip; +using SharpCompress.Common; +using SharpCompress.Common.Tar.Headers; +using SharpCompress.IO; +using SharpCompress.Readers; +using SharpCompress.Readers.Zip; +using SharpCompress.Writers; +using SharpCompress.Writers.Zip; + +namespace SharpCompress.Factories +{ + /// + /// Represents the foundation factory of ZIP archive. + /// + public class ZipFactory : Factory, + IArchiveFactory, + IMultiArchiveFactory, + IReaderFactory, + IWriterFactory, + IWriteableArchiveFactory + { + #region IFactory + + /// + public override string Name => "Zip"; + + /// + public override ArchiveType? KnownArchiveType => ArchiveType.Zip; + + /// + public override IEnumerable GetSupportedExtensions() + { + yield return "zip"; + yield return "zipx"; + yield return "cbz"; + } + + /// + public override bool IsArchive(Stream stream, string? password = null) + { + var startPosition = stream.CanSeek + ? stream.Position + : -1; + + // probe for single volume zip + + if (ZipArchive.IsZipFile(stream, password)) + return true; + + // probe for a multipart zip + + if (!stream.CanSeek) + return false; + + stream.Position = startPosition; + + //test the zip (last) file of a multipart zip + if (ZipArchive.IsZipMulti(stream)) + return true; + + return false; + } + + /// + public override FileInfo? GetFilePart(int index, FileInfo part1) + { + return ZipArchiveVolumeFactory.GetFilePart(index, part1); + } + + #endregion + + #region IArchiveFactory + + /// + 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); + } + + #endregion + + #region IMultiArchiveFactory + + /// + public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) + { + return ZipArchive.Open(streams, readerOptions); + } + + /// + public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) + { + return ZipArchive.Open(fileInfos, readerOptions); + } + + #endregion + + #region IReaderFactory + + /// + public IReader OpenReader(Stream stream, ReaderOptions? options) + { + return ZipReader.Open(stream, options); + } + + #endregion + + #region IWriterFactory + + /// + public IWriter Open(Stream stream, WriterOptions writerOptions) + { + return new ZipWriter(stream, new ZipWriterOptions(writerOptions)); + } + + #endregion + + #region IWriteableArchiveFactory + + /// + public IWritableArchive CreateWriteableArchive() + { + return ZipArchive.Create(); + } + + #endregion + } +} diff --git a/src/SharpCompress/Readers/IReaderFactory.cs b/src/SharpCompress/Readers/IReaderFactory.cs new file mode 100644 index 00000000..1f6f7408 --- /dev/null +++ b/src/SharpCompress/Readers/IReaderFactory.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using SharpCompress.IO; + +namespace SharpCompress.Readers +{ + public interface IReaderFactory : Factories.IFactory + { + /// + /// Opens a Reader for Non-seeking usage + /// + /// + /// + /// + IReader OpenReader(Stream stream, ReaderOptions? options); + } +} + diff --git a/src/SharpCompress/Readers/ReaderFactory.cs b/src/SharpCompress/Readers/ReaderFactory.cs index adff51cc..5f75ed0d 100644 --- a/src/SharpCompress/Readers/ReaderFactory.cs +++ b/src/SharpCompress/Readers/ReaderFactory.cs @@ -1,20 +1,9 @@ using System; using System.IO; -using SharpCompress.Archives.GZip; -using SharpCompress.Archives.Rar; -using SharpCompress.Archives.Tar; -using SharpCompress.Archives.Zip; +using System.Linq; + using SharpCompress.Common; -using SharpCompress.Compressors; -using SharpCompress.Compressors.BZip2; -using SharpCompress.Compressors.Deflate; using SharpCompress.IO; -using SharpCompress.Readers.GZip; -using SharpCompress.Readers.Rar; -using SharpCompress.Readers.Tar; -using SharpCompress.Readers.Zip; -using SharpCompress.Compressors.LZMA; -using SharpCompress.Compressors.Xz; namespace SharpCompress.Readers { @@ -33,74 +22,15 @@ namespace SharpCompress.Readers { LeaveStreamOpen = false }; + RewindableStream rewindableStream = new RewindableStream(stream); rewindableStream.StartRecording(); - if (ZipArchive.IsZipFile(rewindableStream, options.Password)) - { - rewindableStream.Rewind(true); - return ZipReader.Open(rewindableStream, options); - } - rewindableStream.Rewind(false); - if (GZipArchive.IsGZipFile(rewindableStream)) - { - rewindableStream.Rewind(false); - GZipStream testStream = new GZipStream(rewindableStream, CompressionMode.Decompress); - if (TarArchive.IsTarFile(testStream)) - { - rewindableStream.Rewind(true); - return new TarReader(rewindableStream, options, CompressionType.GZip); - } - rewindableStream.Rewind(true); - return GZipReader.Open(rewindableStream, options); - } - rewindableStream.Rewind(false); - if (BZip2Stream.IsBZip2(rewindableStream)) + foreach(var factory in Factories.Factory.Factories.OfType()) { - rewindableStream.Rewind(false); - BZip2Stream testStream = new BZip2Stream(NonDisposingStream.Create(rewindableStream), CompressionMode.Decompress, false); - if (TarArchive.IsTarFile(testStream)) - { - rewindableStream.Rewind(true); - return new TarReader(rewindableStream, options, CompressionType.BZip2); - } - } + if (factory.TryOpenReader(rewindableStream, options, out var reader) && reader != null) return reader; + } - rewindableStream.Rewind(false); - if (LZipStream.IsLZipFile(rewindableStream)) - { - rewindableStream.Rewind(false); - LZipStream testStream = new LZipStream(NonDisposingStream.Create(rewindableStream), CompressionMode.Decompress); - if (TarArchive.IsTarFile(testStream)) - { - rewindableStream.Rewind(true); - return new TarReader(rewindableStream, options, CompressionType.LZip); - } - } - rewindableStream.Rewind(false); - if (RarArchive.IsRarFile(rewindableStream, options)) - { - rewindableStream.Rewind(true); - return RarReader.Open(rewindableStream, options); - } - - rewindableStream.Rewind(false); - if (TarArchive.IsTarFile(rewindableStream)) - { - rewindableStream.Rewind(true); - return TarReader.Open(rewindableStream, options); - } - rewindableStream.Rewind(false); - if (XZStream.IsXZStream(rewindableStream)) - { - rewindableStream.Rewind(true); - XZStream testStream = new XZStream(rewindableStream); - if (TarArchive.IsTarFile(testStream)) - { - rewindableStream.Rewind(true); - return new TarReader(rewindableStream, options, CompressionType.Xz); - } - } throw new InvalidOperationException("Cannot determine compressed stream type. Supported Reader Formats: Zip, GZip, BZip2, Tar, Rar, LZip, XZ"); } } diff --git a/src/SharpCompress/Writers/IWriterFactory.cs b/src/SharpCompress/Writers/IWriterFactory.cs new file mode 100644 index 00000000..1aed7c72 --- /dev/null +++ b/src/SharpCompress/Writers/IWriterFactory.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using SharpCompress.Factories; + +namespace SharpCompress.Writers +{ + public interface IWriterFactory : IFactory + { + IWriter Open(Stream stream, WriterOptions writerOptions); + } +} diff --git a/src/SharpCompress/Writers/WriterFactory.cs b/src/SharpCompress/Writers/WriterFactory.cs index fcb0fd41..60aa613c 100644 --- a/src/SharpCompress/Writers/WriterFactory.cs +++ b/src/SharpCompress/Writers/WriterFactory.cs @@ -1,9 +1,8 @@ -using System; +using System; using System.IO; +using System.Linq; + using SharpCompress.Common; -using SharpCompress.Writers.GZip; -using SharpCompress.Writers.Tar; -using SharpCompress.Writers.Zip; namespace SharpCompress.Writers { @@ -11,29 +10,15 @@ namespace SharpCompress.Writers { public static IWriter Open(Stream stream, ArchiveType archiveType, WriterOptions writerOptions) { - switch (archiveType) - { - case ArchiveType.GZip: - { - if (writerOptions.CompressionType != CompressionType.GZip) - { - throw new InvalidFormatException("GZip archives only support GZip compression type."); - } - return new GZipWriter(stream, new GZipWriterOptions(writerOptions)); - } - case ArchiveType.Zip: - { - return new ZipWriter(stream, new ZipWriterOptions(writerOptions)); - } - case ArchiveType.Tar: - { - return new TarWriter(stream, new TarWriterOptions(writerOptions)); - } - default: - { - throw new NotSupportedException("Archive Type does not have a Writer: " + archiveType); - } - } + var factory = Factories.Factory + .Factories + .OfType() + .FirstOrDefault(item => item.KnownArchiveType == archiveType); + + if (factory != null) + return factory.Open(stream, writerOptions); + + throw new NotSupportedException("Archive Type does not have a Writer: " + archiveType); } } -} \ No newline at end of file +}