Merge pull request #945 from mitchcapper/archive_extension_hinting_pr

Extension hinting for ReaderFactory for better first try factory success
This commit is contained in:
Adam Hathcock
2025-09-29 09:02:11 +01:00
committed by GitHub
3 changed files with 159 additions and 94 deletions

View File

@@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Archives;
using SharpCompress.Archives.Tar;
using SharpCompress.Common;
using SharpCompress.Compressors;
using SharpCompress.Compressors.BZip2;
using SharpCompress.Compressors.Deflate;
using SharpCompress.Compressors.LZMA;
using SharpCompress.Compressors.Lzw;
using SharpCompress.Compressors.Xz;
@@ -14,6 +17,7 @@ using SharpCompress.Readers;
using SharpCompress.Readers.Tar;
using SharpCompress.Writers;
using SharpCompress.Writers.Tar;
using GZipArchive = SharpCompress.Archives.GZip.GZipArchive;
namespace SharpCompress.Factories;
@@ -39,32 +43,13 @@ public class TarFactory
/// <inheritdoc/>
public override IEnumerable<string> 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";
foreach (var testOption in compressionOptions)
{
foreach (var ext in testOption.KnownExtensions)
{
yield return ext;
}
}
}
/// <inheritdoc/>
@@ -102,6 +87,77 @@ public class TarFactory
#region IReaderFactory
protected class TestOption
{
public readonly CompressionType Type;
public readonly Func<Stream, bool> CanHandle;
public readonly bool WrapInSharpCompressStream;
public readonly Func<Stream, Stream> CreateStream;
public readonly IEnumerable<string> KnownExtensions;
public TestOption(
CompressionType Type,
Func<Stream, bool> CanHandle,
Func<Stream, Stream> CreateStream,
IEnumerable<string> KnownExtensions,
bool WrapInSharpCompressStream = true
)
{
this.Type = Type;
this.CanHandle = CanHandle;
this.WrapInSharpCompressStream = WrapInSharpCompressStream;
this.CreateStream = CreateStream;
this.KnownExtensions = KnownExtensions;
}
}
// https://en.wikipedia.org/wiki/Tar_(computing)#Suffixes_for_compressed_files
protected TestOption[] compressionOptions =
[
new(CompressionType.None, (stream) => true, (stream) => stream, ["tar"], false), // We always do a test for IsTarFile later
new(
CompressionType.BZip2,
BZip2Stream.IsBZip2,
(stream) => new BZip2Stream(stream, CompressionMode.Decompress, false),
["tar.bz2", "tb2", "tbz", "tbz2", "tz2"]
),
new(
CompressionType.GZip,
GZipArchive.IsGZipFile,
(stream) => new GZipStream(stream, CompressionMode.Decompress),
["tar.gz", "taz", "tgz"]
),
new(
CompressionType.ZStandard,
ZStandardStream.IsZStandard,
(stream) => new ZStandardStream(stream),
["tar.zst", "tar.zstd", "tzst", "tzstd"]
),
new(
CompressionType.LZip,
LZipStream.IsLZipFile,
(stream) => new LZipStream(stream, CompressionMode.Decompress),
["tar.lz"]
),
new(
CompressionType.Xz,
XZStream.IsXZStream,
(stream) => new XZStream(stream),
["tar.xz", "txz"],
false
),
new(
CompressionType.Lzw,
LzwStream.IsLzwStream,
(stream) => new LzwStream(stream),
["tar.Z", "tZ", "taZ"],
false
),
];
/// <inheritdoc/>
internal override bool TryOpenReader(
SharpCompressStream rewindableStream,
@@ -111,84 +167,35 @@ public class TarFactory
{
reader = null;
long pos = ((IStreamStack)rewindableStream).GetPosition();
if (TarArchive.IsTarFile(rewindableStream))
TestOption? testedOption = null;
if (!string.IsNullOrWhiteSpace(options.ExtensionHint))
{
((IStreamStack)rewindableStream).StackSeek(pos);
reader = OpenReader(rewindableStream, options);
return true;
}
((IStreamStack)rewindableStream).StackSeek(pos);
if (BZip2Stream.IsBZip2(rewindableStream))
{
((IStreamStack)rewindableStream).StackSeek(pos);
var testStream = new BZip2Stream(
SharpCompressStream.Create(rewindableStream, leaveOpen: true),
CompressionMode.Decompress,
false
testedOption = compressionOptions.FirstOrDefault(a =>
a.KnownExtensions.Contains(
options.ExtensionHint,
StringComparer.CurrentCultureIgnoreCase
)
);
if (TarArchive.IsTarFile(testStream))
if (testedOption != null)
{
((IStreamStack)rewindableStream).StackSeek(pos);
reader = new TarReader(rewindableStream, options, CompressionType.BZip2);
return true;
reader = TryOption(rewindableStream, options, pos, testedOption);
if (reader != null)
{
return true;
}
}
}
((IStreamStack)rewindableStream).StackSeek(pos);
if (ZStandardStream.IsZStandard(rewindableStream))
foreach (var testOption in compressionOptions)
{
((IStreamStack)rewindableStream).StackSeek(pos);
var testStream = new ZStandardStream(
SharpCompressStream.Create(rewindableStream, leaveOpen: true)
);
if (TarArchive.IsTarFile(testStream))
if (testedOption == testOption)
{
((IStreamStack)rewindableStream).StackSeek(pos);
reader = new TarReader(rewindableStream, options, CompressionType.ZStandard);
return true;
continue; // Already tested above
}
}
((IStreamStack)rewindableStream).StackSeek(pos);
if (LZipStream.IsLZipFile(rewindableStream))
{
((IStreamStack)rewindableStream).StackSeek(pos);
var testStream = new LZipStream(
SharpCompressStream.Create(rewindableStream, leaveOpen: true),
CompressionMode.Decompress
);
if (TarArchive.IsTarFile(testStream))
reader = TryOption(rewindableStream, options, pos, testOption);
if (reader != null)
{
((IStreamStack)rewindableStream).StackSeek(pos);
reader = new TarReader(rewindableStream, options, CompressionType.LZip);
return true;
}
}
((IStreamStack)rewindableStream).StackSeek(pos);
if (XZStream.IsXZStream(rewindableStream))
{
((IStreamStack)rewindableStream).StackSeek(pos);
var testStream = new XZStream(rewindableStream);
if (TarArchive.IsTarFile(testStream))
{
((IStreamStack)rewindableStream).StackSeek(pos);
reader = new TarReader(rewindableStream, options, CompressionType.Xz);
return true;
}
}
((IStreamStack)rewindableStream).StackSeek(pos);
if (LzwStream.IsLzwStream(rewindableStream))
{
var testStream = new LzwStream(rewindableStream);
((IStreamStack)rewindableStream).StackSeek(pos);
if (TarArchive.IsTarFile(testStream))
{
((IStreamStack)rewindableStream).StackSeek(pos);
reader = new TarReader(rewindableStream, options, CompressionType.Lzw);
return true;
}
}
@@ -196,6 +203,33 @@ public class TarFactory
return false;
}
private static IReader? TryOption(
SharpCompressStream rewindableStream,
ReaderOptions options,
long pos,
TestOption testOption
)
{
if (testOption.CanHandle(rewindableStream))
{
((IStreamStack)rewindableStream).StackSeek(pos);
var inStream = rewindableStream;
if (testOption.WrapInSharpCompressStream)
{
inStream = SharpCompressStream.Create(rewindableStream, leaveOpen: true);
}
var testStream = testOption.CreateStream(rewindableStream);
if (TarArchive.IsTarFile(testStream))
{
((IStreamStack)rewindableStream).StackSeek(pos);
return new TarReader(rewindableStream, options, testOption.Type);
}
}
return null;
}
/// <inheritdoc/>
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
TarReader.Open(stream, options);

View File

@@ -1,6 +1,8 @@
using System;
using System.IO;
using System.Linq;
using SharpCompress.Common;
using SharpCompress.Factories;
using SharpCompress.IO;
namespace SharpCompress.Readers;
@@ -22,8 +24,32 @@ public static class ReaderFactory
long pos = ((IStreamStack)bStream).GetPosition();
foreach (var factory in Factories.Factory.Factories.OfType<Factories.Factory>())
var factories = Factories.Factory.Factories.OfType<Factories.Factory>();
Factory? testedFactory = null;
if (!string.IsNullOrWhiteSpace(options.ExtensionHint))
{
testedFactory = factories.FirstOrDefault(a =>
a.GetSupportedExtensions()
.Contains(options.ExtensionHint, StringComparer.CurrentCultureIgnoreCase)
);
if (
testedFactory?.TryOpenReader(bStream, options, out var reader) == true
&& reader != null
)
{
return reader;
}
((IStreamStack)bStream).StackSeek(pos);
}
foreach (var factory in factories)
{
if (testedFactory == factory)
{
continue; // Already tested above
}
((IStreamStack)bStream).StackSeek(pos);
if (factory.TryOpenReader(bStream, options, out var reader) && reader != null)
{

View File

@@ -16,4 +16,9 @@ public class ReaderOptions : OptionsBase
public bool DisableCheckIncomplete { get; set; }
public int BufferSize { get; set; } = DefaultBufferSize;
/// <summary>
/// Provide a hint for the extension of the archive being read, can speed up finding the correct decoder. Should be without the leading period in the form like: tar.gz or zip
/// </summary>
public string? ExtensionHint { get; set; }
}