From af7e270b2d02f3c4f13b58ca8f00e6d84cf4f480 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 27 Nov 2025 12:11:10 +0000 Subject: [PATCH] added SupportsMultiThreading flag for File based Zips --- .../Archives/Zip/ZipArchiveEntry.cs | 2 ++ src/SharpCompress/Common/Entry.cs | 1 + src/SharpCompress/Common/FilePart.cs | 3 ++ src/SharpCompress/Common/IEntry.cs | 1 + .../Common/Zip/SeekableZipFilePart.cs | 26 +++++++------- src/SharpCompress/IO/SourceStream.cs | 34 ++++++++++--------- tests/SharpCompress.Test/ArchiveTests.cs | 2 ++ 7 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs b/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs index a6baf34b..c15879c5 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs @@ -23,5 +23,7 @@ public class ZipArchiveEntry : ZipEntry, IArchiveEntry public bool IsComplete => true; + public override bool SupportsMultiThreading => Parts.Single().SupportsMultiThreading; + #endregion } diff --git a/src/SharpCompress/Common/Entry.cs b/src/SharpCompress/Common/Entry.cs index 6209b3de..8e091ed9 100644 --- a/src/SharpCompress/Common/Entry.cs +++ b/src/SharpCompress/Common/Entry.cs @@ -87,4 +87,5 @@ public abstract class Entry : IEntry /// Entry file attribute. /// public virtual int? Attrib => throw new NotImplementedException(); + public virtual bool SupportsMultiThreading => false; } diff --git a/src/SharpCompress/Common/FilePart.cs b/src/SharpCompress/Common/FilePart.cs index 54e3c9f9..5096d49c 100644 --- a/src/SharpCompress/Common/FilePart.cs +++ b/src/SharpCompress/Common/FilePart.cs @@ -14,4 +14,7 @@ public abstract class FilePart internal abstract Stream? GetCompressedStream(); internal abstract Stream? GetRawStream(); internal bool Skipped { get; set; } + + public virtual bool SupportsMultiThreading => false; } + diff --git a/src/SharpCompress/Common/IEntry.cs b/src/SharpCompress/Common/IEntry.cs index 56e1db81..3d0183d8 100644 --- a/src/SharpCompress/Common/IEntry.cs +++ b/src/SharpCompress/Common/IEntry.cs @@ -21,4 +21,5 @@ public interface IEntry DateTime? LastModifiedTime { get; } long Size { get; } int? Attrib { get; } + public bool SupportsMultiThreading { get; } } diff --git a/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs b/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs index 6f88bb4b..418a8365 100644 --- a/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs @@ -1,20 +1,10 @@ +using System; using System.IO; using SharpCompress.Common.Zip.Headers; +using SharpCompress.IO; namespace SharpCompress.Common.Zip; -internal class FileInfoZipFilePart : FilePart -{ - internal FileInfoZipFilePart( - DirectoryEntryHeader header, - FileInfo file - ) - : base(header.ArchiveEncoding) - { - } - -} - internal class SeekableZipFilePart : ZipFilePart { private bool _isLocalHeaderLoaded; @@ -42,8 +32,20 @@ internal class SeekableZipFilePart : ZipFilePart protected override Stream CreateBaseStream() { + if (BaseStream is SourceStream ss) + { + if (ss.IsFileMode) + { + var fileStream = ss.CurrentFile.OpenRead(); + fileStream.Position = Header.DataStartPosition.NotNull(); + return fileStream; + } + } BaseStream.Position = Header.DataStartPosition.NotNull(); return BaseStream; } + + public override bool SupportsMultiThreading => BaseStream is SourceStream ss && ss.IsFileMode; + } diff --git a/src/SharpCompress/IO/SourceStream.cs b/src/SharpCompress/IO/SourceStream.cs index 0712d915..cddc7cf6 100644 --- a/src/SharpCompress/IO/SourceStream.cs +++ b/src/SharpCompress/IO/SourceStream.cs @@ -15,7 +15,7 @@ public class SourceStream : Stream, IStreamStack #endif int IStreamStack.DefaultBufferSize { get; set; } - Stream IStreamStack.BaseStream() => _streams[_stream]; + Stream IStreamStack.BaseStream() => _streams[_streamIndex]; int IStreamStack.BufferSize { @@ -35,7 +35,7 @@ public class SourceStream : Stream, IStreamStack private readonly List _streams; private readonly Func? _getFilePart; private readonly Func? _getStreamPart; - private int _stream; + private int _streamIndex; public SourceStream(FileInfo file, Func getPart, ReaderOptions options) : this(null, null, file, getPart, options) { } @@ -59,7 +59,7 @@ public class SourceStream : Stream, IStreamStack if (!IsFileMode) { - _streams.Add(stream!); + _streams.Add(stream.NotNull("stream is null")); _getStreamPart = getStreamPart; _getFilePart = _ => null; if (stream is FileStream fileStream) @@ -69,12 +69,12 @@ public class SourceStream : Stream, IStreamStack } else { - _files.Add(file!); + _files.Add(file.NotNull("file is null")); _streams.Add(_files[0].OpenRead()); _getFilePart = getFilePart; _getStreamPart = _ => null; } - _stream = 0; + _streamIndex = 0; _prevSize = 0; #if DEBUG_STREAMS @@ -93,10 +93,12 @@ public class SourceStream : Stream, IStreamStack public ReaderOptions ReaderOptions { get; } public bool IsFileMode { get; } - public IEnumerable Files => _files; - public IEnumerable Streams => _streams; + public IReadOnlyList Files => _files; + public IReadOnlyList Streams => _streams; - private Stream Current => _streams[_stream]; + private Stream Current => _streams[_streamIndex]; + + public FileInfo CurrentFile => _files[_streamIndex]; public bool LoadStream(int index) //ensure all parts to id are loaded { @@ -107,7 +109,7 @@ public class SourceStream : Stream, IStreamStack var f = _getFilePart.NotNull("GetFilePart is null")(_streams.Count); if (f == null) { - _stream = _streams.Count - 1; + _streamIndex = _streams.Count - 1; return false; } //throw new Exception($"File part {idx} not available."); @@ -119,7 +121,7 @@ public class SourceStream : Stream, IStreamStack var s = _getStreamPart.NotNull("GetStreamPart is null")(_streams.Count); if (s == null) { - _stream = _streams.Count - 1; + _streamIndex = _streams.Count - 1; return false; } //throw new Exception($"Stream part {idx} not available."); @@ -137,10 +139,10 @@ public class SourceStream : Stream, IStreamStack { if (LoadStream(idx)) { - _stream = idx; + _streamIndex = idx; } - return _stream == idx; + return _streamIndex == idx; } public override bool CanRead => true; @@ -184,7 +186,7 @@ public class SourceStream : Stream, IStreamStack var length = Current.Length; // Load next file if present - if (!SetStream(_stream + 1)) + if (!SetStream(_streamIndex + 1)) { break; } @@ -223,7 +225,7 @@ public class SourceStream : Stream, IStreamStack while (_prevSize + Current.Length < pos) { _prevSize += Current.Length; - SetStream(_stream + 1); + SetStream(_streamIndex + 1); } } @@ -273,7 +275,7 @@ public class SourceStream : Stream, IStreamStack var length = Current.Length; // Load next file if present - if (!SetStream(_stream + 1)) + if (!SetStream(_streamIndex + 1)) { break; } @@ -322,7 +324,7 @@ public class SourceStream : Stream, IStreamStack var length = Current.Length; // Load next file if present - if (!SetStream(_stream + 1)) + if (!SetStream(_streamIndex + 1)) { break; } diff --git a/tests/SharpCompress.Test/ArchiveTests.cs b/tests/SharpCompress.Test/ArchiveTests.cs index 2f47e121..8a3e1a97 100644 --- a/tests/SharpCompress.Test/ArchiveTests.cs +++ b/tests/SharpCompress.Test/ArchiveTests.cs @@ -134,6 +134,7 @@ public class ArchiveTests : ReaderTests { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { + Assert.False(entry.SupportsMultiThreading); entry.WriteToDirectory( SCRATCH_FILES_PATH, new ExtractionOptions { ExtractFullPath = true, Overwrite = true } @@ -277,6 +278,7 @@ public class ArchiveTests : ReaderTests { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { + Assert.True(entry.SupportsMultiThreading); entry.WriteToDirectory( SCRATCH_FILES_PATH, new ExtractionOptions { ExtractFullPath = true, Overwrite = true }