Compare commits

...

3 Commits

Author SHA1 Message Date
Adam Hathcock
2e301becb4 fmt 2025-11-27 15:50:25 +00:00
Adam Hathcock
2e7d4eb74b first pass of making Rar multi-threaded 2025-11-27 15:49:29 +00:00
Adam Hathcock
5b2030bb98 add SupportsMultiThreading and IsMultiVolume to archive 2025-11-27 15:07:13 +00:00
18 changed files with 97 additions and 100 deletions

View File

@@ -172,4 +172,9 @@ public abstract class AbstractArchive<TEntry, TVolume> : IArchive, IArchiveExtra
return Entries.All(x => x.IsComplete);
}
}
public virtual bool IsMultiVolume =>
_sourceStream?.Files.Count > 1 || _sourceStream?.Streams.Count > 1;
public virtual bool SupportsMultiThreading => false;
}

View File

@@ -45,4 +45,14 @@ public interface IArchive : IDisposable
/// The total size of the files as uncompressed in the archive.
/// </summary>
long TotalUncompressSize { get; }
/// <summary>
/// Is the archive part of a multi-volume set.
/// </summary>
bool IsMultiVolume { get; }
/// <summary>
/// Does the archive support multi-threaded extraction.
/// </summary>
bool SupportsMultiThreading { get; }
}

View File

@@ -124,10 +124,10 @@ public static class IArchiveEntryExtensions
entry,
destinationFileName,
options,
async (x, fm) =>
async (x, fm, ct) =>
{
using var fs = File.Open(destinationFileName, fm);
await entry.WriteToAsync(fs, cancellationToken).ConfigureAwait(false);
await entry.WriteToAsync(fs, ct).ConfigureAwait(false);
},
cancellationToken
);

View File

@@ -1,39 +0,0 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using SharpCompress.Common.Rar;
using SharpCompress.Common.Rar.Headers;
using SharpCompress.IO;
using SharpCompress.Readers;
namespace SharpCompress.Archives.Rar;
/// <summary>
/// A rar part based on a FileInfo object
/// </summary>
internal class FileInfoRarArchiveVolume : RarVolume
{
internal FileInfoRarArchiveVolume(FileInfo fileInfo, ReaderOptions options, int index)
: base(StreamingMode.Seekable, fileInfo.OpenRead(), FixOptions(options), index)
{
FileInfo = fileInfo;
FileParts = GetVolumeFileParts().ToArray().ToReadOnly();
}
private static ReaderOptions FixOptions(ReaderOptions options)
{
//make sure we're closing streams with fileinfo
options.LeaveStreamOpen = false;
return options;
}
internal ReadOnlyCollection<RarFilePart> FileParts { get; }
internal FileInfo FileInfo { get; }
internal override RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader) =>
new FileInfoRarFilePart(this, ReaderOptions.Password, markHeader, fileHeader, FileInfo);
internal override IEnumerable<RarFilePart> ReadFileParts() => FileParts;
}

View File

@@ -1,21 +0,0 @@
using System.IO;
using SharpCompress.Common.Rar.Headers;
namespace SharpCompress.Archives.Rar;
internal sealed class FileInfoRarFilePart : SeekableFilePart
{
internal FileInfoRarFilePart(
FileInfoRarArchiveVolume volume,
string? password,
MarkHeader mh,
FileHeader fh,
FileInfo fi
)
: base(mh, fh, volume.Index, volume.Stream, password) => FileInfo = fi;
internal FileInfo FileInfo { get; }
internal override string FilePartName =>
"Rar File: " + FileInfo.FullName + " File Entry: " + FileHeader.FileName;
}

View File

@@ -47,9 +47,9 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
{
sourceStream.LoadAllParts(); //request all streams
var streams = sourceStream.Streams.ToArray();
var i = 0;
if (streams.Length > 1 && IsRarFile(streams[1], ReaderOptions)) //test part 2 - true = multipart not split
{
var i = 0;
sourceStream.IsVolumes = true;
streams[1].Position = 0;
sourceStream.Position = 0;
@@ -57,12 +57,18 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
return sourceStream.Streams.Select(a => new StreamRarArchiveVolume(
a,
ReaderOptions,
i++
i++,
IsMultiVolume
));
}
//split mode or single file
return new StreamRarArchiveVolume(sourceStream, ReaderOptions, i++).AsEnumerable();
return new StreamRarArchiveVolume(
sourceStream,
ReaderOptions,
0,
IsMultiVolume
).AsEnumerable();
}
protected override IReader CreateReaderForSolidExtraction()
@@ -83,6 +89,7 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
}
public override bool IsSolid => Volumes.First().IsSolidArchive;
public override bool SupportsMultiThreading => !IsMultiVolume && !IsSolid;
public virtual int MinVersion => Volumes.First().MinVersion;
public virtual int MaxVersion => Volumes.First().MaxVersion;

View File

@@ -134,4 +134,6 @@ public class RarArchiveEntry : RarEntry, IArchiveEntry
);
}
}
public override bool SupportsMultiThreading => Parts.Single().SupportsMultiThreading;
}

View File

@@ -1,25 +1,29 @@
using System.IO;
using SharpCompress.Common.Rar;
using SharpCompress.Common.Rar.Headers;
using SharpCompress.IO;
namespace SharpCompress.Archives.Rar;
internal class SeekableFilePart : RarFilePart
internal class SeekableRarFilePart : RarFilePart
{
private readonly Stream _stream;
private readonly string? _password;
private readonly bool _isMultiVolume;
internal SeekableFilePart(
internal SeekableRarFilePart(
MarkHeader mh,
FileHeader fh,
int index,
Stream stream,
string? password
string? password,
bool isMultiVolume
)
: base(mh, fh, index)
{
_stream = stream;
_password = password;
_isMultiVolume = isMultiVolume;
}
internal override Stream GetCompressedStream()
@@ -42,4 +46,7 @@ internal class SeekableFilePart : RarFilePart
}
internal override string FilePartName => "Unknown Stream - File Entry: " + FileHeader.FileName;
public override bool SupportsMultiThreading =>
!_isMultiVolume && _stream is SourceStream ss && ss.IsFileMode && ss.Files.Count == 1;
}

View File

@@ -9,11 +9,28 @@ namespace SharpCompress.Archives.Rar;
internal class StreamRarArchiveVolume : RarVolume
{
internal StreamRarArchiveVolume(Stream stream, ReaderOptions options, int index)
: base(StreamingMode.Seekable, stream, options, index) { }
private readonly bool _isMultiVolume;
internal StreamRarArchiveVolume(
Stream stream,
ReaderOptions options,
int index,
bool isMultiVolume
)
: base(StreamingMode.Seekable, stream, options, index)
{
_isMultiVolume = isMultiVolume;
}
internal override IEnumerable<RarFilePart> ReadFileParts() => GetVolumeFileParts();
internal override RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader) =>
new SeekableFilePart(markHeader, fileHeader, Index, Stream, ReaderOptions.Password);
new SeekableRarFilePart(
markHeader,
fileHeader,
Index,
Stream,
ReaderOptions.Password,
_isMultiVolume
);
}

View File

@@ -283,7 +283,12 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
yield return new ZipArchiveEntry(
this,
new SeekableZipFilePart(headerFactory.NotNull(), deh, s)
new SeekableZipFilePart(
headerFactory.NotNull(),
deh,
s,
IsMultiVolume
)
);
}
break;
@@ -385,4 +390,6 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
((IStreamStack)stream).StackSeek(0);
return ZipReader.Open(stream, ReaderOptions, Entries);
}
public override bool SupportsMultiThreading => !IsMultiVolume;
}

View File

@@ -201,7 +201,7 @@ internal static class ExtractionMethods
IEntry entry,
string destinationFileName,
ExtractionOptions? options,
Func<string, FileMode, Task> openAndWriteAsync,
Func<string, FileMode, CancellationToken, Task> openAndWriteAsync,
CancellationToken cancellationToken = default
)
{
@@ -225,7 +225,8 @@ internal static class ExtractionMethods
fm = FileMode.CreateNew;
}
await openAndWriteAsync(destinationFileName, fm).ConfigureAwait(false);
await openAndWriteAsync(destinationFileName, fm, cancellationToken)
.ConfigureAwait(false);
entry.PreserveExtractionOptions(destinationFileName, options);
}
}

View File

@@ -21,5 +21,5 @@ public interface IEntry
DateTime? LastModifiedTime { get; }
long Size { get; }
int? Attrib { get; }
public bool SupportsMultiThreading { get; }
bool SupportsMultiThreading { get; }
}

View File

@@ -1,4 +1,3 @@
using System;
using System.IO;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.IO;
@@ -9,13 +8,19 @@ internal class SeekableZipFilePart : ZipFilePart
{
private bool _isLocalHeaderLoaded;
private readonly SeekableZipHeaderFactory _headerFactory;
private readonly bool _isMultiVolume;
internal SeekableZipFilePart(
SeekableZipHeaderFactory headerFactory,
DirectoryEntryHeader header,
Stream stream
Stream stream,
bool isMultiVolume
)
: base(header, stream) => _headerFactory = headerFactory;
: base(header, stream)
{
_headerFactory = headerFactory;
_isMultiVolume = isMultiVolume;
}
internal override Stream GetCompressedStream()
{
@@ -32,9 +37,9 @@ internal class SeekableZipFilePart : ZipFilePart
protected override Stream CreateBaseStream()
{
if (BaseStream is SourceStream ss)
if (!_isMultiVolume && BaseStream is SourceStream ss)
{
if (ss.IsFileMode)
if (ss.IsFileMode && ss.Files.Count == 1)
{
var fileStream = ss.CurrentFile.OpenRead();
fileStream.Position = Header.DataStartPosition.NotNull();
@@ -46,5 +51,6 @@ internal class SeekableZipFilePart : ZipFilePart
return BaseStream;
}
public override bool SupportsMultiThreading => BaseStream is SourceStream ss && ss.IsFileMode;
public override bool SupportsMultiThreading =>
!_isMultiVolume && BaseStream is SourceStream ss && ss.IsFileMode && ss.Files.Count == 1;
}

View File

@@ -62,10 +62,6 @@ internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack
base.Dispose(disposing);
if (disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(MultiVolumeReadOnlyStream));
#endif
if (filePartEnumerator != null)
{
filePartEnumerator.Dispose();

View File

@@ -82,9 +82,6 @@ internal class RarStream : Stream, IStreamStack
{
if (disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(RarStream));
#endif
ArrayPool<byte>.Shared.Return(this.tmpBuffer);
this.tmpBuffer = null;
}

View File

@@ -101,10 +101,10 @@ public static class IReaderExtensions
reader.Entry,
destinationFileName,
options,
async (x, fm) =>
async (x, fm, ct) =>
{
using var fs = File.Open(destinationFileName, fm);
await reader.WriteEntryToAsync(fs, cancellationToken).ConfigureAwait(false);
await reader.WriteEntryToAsync(fs, ct).ConfigureAwait(false);
},
cancellationToken
)

View File

@@ -277,13 +277,10 @@ public class ArchiveTests : ReaderTests
var tasks = new List<Task>();
using (var archive = archiveFactory.Open(new FileInfo(testArchive), readerOptions))
{
Assert.True(archive.SupportsMultiThreading);
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
{
if (archiveFactory.KnownArchiveType == ArchiveType.Zip)
{
Assert.True(entry.SupportsMultiThreading);
}
Assert.True(entry.SupportsMultiThreading);
var t = entry.WriteToDirectoryAsync(
SCRATCH_FILES_PATH,
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
@@ -306,11 +303,6 @@ public class ArchiveTests : ReaderTests
{
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
{
if (archiveFactory.KnownArchiveType == ArchiveType.Zip)
{
Assert.True(entry.SupportsMultiThreading);
}
entry.WriteToDirectory(
SCRATCH_FILES_PATH,
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }

View File

@@ -1,5 +1,6 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using SharpCompress.Archives;
using SharpCompress.Archives.Rar;
using SharpCompress.Common;
@@ -292,9 +293,15 @@ public class RarArchiveTests : ArchiveTests
[Fact]
public void Rar_ArchiveFileRead() => ArchiveFileRead("Rar.rar");
[Fact]
public Task Rar_ArchiveFileRead_Multithreaded() => ArchiveFileRead_Multithreaded("Rar.rar");
[Fact]
public void Rar5_ArchiveFileRead() => ArchiveFileRead("Rar5.rar");
[Fact]
public Task Rar5_ArchiveFileRead_Multithreaded() => ArchiveFileRead_Multithreaded("Rar5.rar");
[Fact]
public void Rar_ArchiveFileRead_HasDirectories() =>
DoRar_ArchiveFileRead_HasDirectories("Rar.rar");
@@ -359,6 +366,9 @@ public class RarArchiveTests : ArchiveTests
[Fact]
public void Rar2_ArchiveFileRead() => ArchiveFileRead("Rar2.rar");
[Fact]
public Task Rar2_ArchiveFileRead_Multithreaded() => ArchiveFileRead_Multithreaded("Rar2.rar");
[Fact]
public void Rar15_ArchiveFileRead()
{