make Writable interfaces for archive

This commit is contained in:
Adam Hathcock
2026-01-12 14:57:13 +00:00
parent 38203fb950
commit fce4a96718
22 changed files with 372 additions and 285 deletions

View File

@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using SharpCompress;
using SharpCompress.Common;
using SharpCompress.IO;
using SharpCompress.Readers;

View File

@@ -12,7 +12,7 @@ namespace SharpCompress.Archives;
public abstract class AbstractWritableArchive<TEntry, TVolume>
: AbstractArchive<TEntry, TVolume>,
IWritableArchive
IWritableArchive, IWritableAsyncArchive
where TEntry : IArchiveEntry
where TVolume : IVolume
{
@@ -83,12 +83,12 @@ public abstract class AbstractWritableArchive<TEntry, TVolume>
}
}
void IWritableArchive.RemoveEntry(IArchiveEntry entry) => RemoveEntry((TEntry)entry);
void IWritableArchiveCommon.RemoveEntry(IArchiveEntry entry) => RemoveEntry((TEntry)entry);
public TEntry AddEntry(string key, Stream source, long size = 0, DateTime? modified = null) =>
AddEntry(key, source, false, size, modified);
IArchiveEntry IWritableArchive.AddEntry(
IArchiveEntry IWritableArchiveCommon.AddEntry(
string key,
Stream source,
bool closeStream,
@@ -96,7 +96,7 @@ public abstract class AbstractWritableArchive<TEntry, TVolume>
DateTime? modified
) => AddEntry(key, source, closeStream, size, modified);
IArchiveEntry IWritableArchive.AddDirectoryEntry(string key, DateTime? modified) =>
IArchiveEntry IWritableArchiveCommon.AddDirectoryEntry(string key, DateTime? modified) =>
AddDirectoryEntry(key, modified);
public TEntry AddEntry(

View File

@@ -88,7 +88,7 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
/// </summary>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null)
public static IWritableArchive Open(Stream stream, ReaderOptions? readerOptions = null)
{
stream.NotNull(nameof(stream));
@@ -108,14 +108,14 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
/// <param name="cancellationToken"></param>
public static IAsyncArchive OpenAsync(
public static IWritableAsyncArchive OpenAsync(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)Open(stream, readerOptions);
return (IWritableAsyncArchive)Open(stream, readerOptions);
}
/// <summary>
@@ -124,14 +124,14 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
/// <param name="fileInfo"></param>
/// <param name="readerOptions"></param>
/// <param name="cancellationToken"></param>
public static IAsyncArchive OpenAsync(
public static IWritableAsyncArchive OpenAsync(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)Open(fileInfo, readerOptions);
return (IWritableAsyncArchive)Open(fileInfo, readerOptions);
}
/// <summary>
@@ -140,14 +140,14 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
/// <param name="cancellationToken"></param>
public static IAsyncArchive OpenAsync(
public static IWritableAsyncArchive OpenAsync(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)Open(streams, readerOptions);
return (IWritableAsyncArchive)Open(streams, readerOptions);
}
/// <summary>
@@ -156,14 +156,14 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
/// <param name="cancellationToken"></param>
public static IAsyncArchive OpenAsync(
public static IWritableAsyncArchive OpenAsync(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)Open(fileInfos, readerOptions);
return (IWritableAsyncArchive)Open(fileInfos, readerOptions);
}
public static GZipArchive Create() => new();

View File

@@ -39,4 +39,6 @@ public interface IArchive : IDisposable
/// The total size of the files as uncompressed in the archive.
/// </summary>
long TotalUncompressSize { get; }
bool IsEncrypted { get; }
}

View File

@@ -6,8 +6,13 @@ using SharpCompress.Writers;
namespace SharpCompress.Archives;
public interface IWritableArchive : IArchive
public interface IWritableArchiveCommon
{
/// <summary>
/// Use this to pause entry rebuilding when adding large collections of entries. Dispose when complete. A using statement is recommended.
/// </summary>
/// <returns>IDisposeable to resume entry rebuilding</returns>
IDisposable PauseEntryRebuilding();
void RemoveEntry(IArchiveEntry entry);
IArchiveEntry AddEntry(
@@ -19,18 +24,20 @@ public interface IWritableArchive : IArchive
);
IArchiveEntry AddDirectoryEntry(string key, DateTime? modified = null);
}
public interface IWritableArchive : IArchive, IWritableArchiveCommon
{
void SaveTo(Stream stream, WriterOptions options);
}
public interface IWritableAsyncArchive : IAsyncArchive, IWritableArchiveCommon
{
ValueTask SaveToAsync(
Stream stream,
WriterOptions options,
CancellationToken cancellationToken = default
);
/// <summary>
/// Use this to pause entry rebuilding when adding large collections of entries. Dispose when complete. A using statement is recommended.
/// </summary>
/// <returns>IDisposeable to resume entry rebuilding</returns>
IDisposable PauseEntryRebuilding();
}

View File

@@ -0,0 +1,75 @@
using System;
using System.IO;
namespace SharpCompress.Archives;
public static class IWritableArchiveCommonExtensions
{
public static void AddEntry(
this IWritableArchive writableArchive,
string entryPath,
string filePath
)
{
var fileInfo = new FileInfo(filePath);
if (!fileInfo.Exists)
{
throw new FileNotFoundException("Could not AddEntry: " + filePath);
}
writableArchive.AddEntry(
entryPath,
new FileInfo(filePath).OpenRead(),
true,
fileInfo.Length,
fileInfo.LastWriteTime
);
}
public static void AddAllFromDirectory(
this IWritableArchiveCommon writableArchive,
string filePath,
string searchPattern = "*.*",
SearchOption searchOption = SearchOption.AllDirectories
)
{
using (writableArchive.PauseEntryRebuilding())
{
foreach (var path in Directory.EnumerateFiles(filePath, searchPattern, searchOption))
{
var fileInfo = new FileInfo(path);
writableArchive.AddEntry(
path.Substring(filePath.Length),
fileInfo.OpenRead(),
true,
fileInfo.Length,
fileInfo.LastWriteTime
);
}
}
}
public static IArchiveEntry AddEntry(
this IWritableArchiveCommon writableArchive,
string key,
string file
) => AddEntry(writableArchive, key, new FileInfo(file));
public static IArchiveEntry AddEntry(
this IWritableArchiveCommon writableArchive,
string key,
FileInfo fileInfo
)
{
if (!fileInfo.Exists)
{
throw new ArgumentException("FileInfo does not exist.");
}
return writableArchive.AddEntry(
key,
fileInfo.OpenRead(),
true,
fileInfo.Length,
fileInfo.LastWriteTime
);
}
}

View File

@@ -1,106 +1,25 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using SharpCompress.Common;
using SharpCompress.Writers;
namespace SharpCompress.Archives;
public static class IWritableArchiveExtensions
{
public static void AddEntry(
this IWritableArchive writableArchive,
string entryPath,
string filePath
)
{
var fileInfo = new FileInfo(filePath);
if (!fileInfo.Exists)
{
throw new FileNotFoundException("Could not AddEntry: " + filePath);
}
writableArchive.AddEntry(
entryPath,
new FileInfo(filePath).OpenRead(),
true,
fileInfo.Length,
fileInfo.LastWriteTime
);
}
public static void SaveTo(
this IWritableArchive writableArchive,
string filePath,
WriterOptions options
) => writableArchive.SaveTo(new FileInfo(filePath), options);
WriterOptions? options = null
) => writableArchive.SaveTo(new FileInfo(filePath), options ?? new (CompressionType.Deflate));
public static void SaveTo(
this IWritableArchive writableArchive,
FileInfo fileInfo,
WriterOptions options
WriterOptions? options = null
)
{
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
writableArchive.SaveTo(stream, options);
}
public static ValueTask SaveToAsync(
this IWritableArchive writableArchive,
string filePath,
WriterOptions options,
CancellationToken cancellationToken = default
) => writableArchive.SaveToAsync(new FileInfo(filePath), options, cancellationToken);
public static async ValueTask SaveToAsync(
this IWritableArchive writableArchive,
FileInfo fileInfo,
WriterOptions options,
CancellationToken cancellationToken = default
)
{
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
await writableArchive.SaveToAsync(stream, options, cancellationToken).ConfigureAwait(false);
}
public static void AddAllFromDirectory(
this IWritableArchive writableArchive,
string filePath,
string searchPattern = "*.*",
SearchOption searchOption = SearchOption.AllDirectories
)
{
using (writableArchive.PauseEntryRebuilding())
{
foreach (var path in Directory.EnumerateFiles(filePath, searchPattern, searchOption))
{
var fileInfo = new FileInfo(path);
writableArchive.AddEntry(
path.Substring(filePath.Length),
fileInfo.OpenRead(),
true,
fileInfo.Length,
fileInfo.LastWriteTime
);
}
}
}
public static IArchiveEntry AddEntry(
this IWritableArchive writableArchive,
string key,
FileInfo fileInfo
)
{
if (!fileInfo.Exists)
{
throw new ArgumentException("FileInfo does not exist.");
}
return writableArchive.AddEntry(
key,
fileInfo.OpenRead(),
true,
fileInfo.Length,
fileInfo.LastWriteTime
);
writableArchive.SaveTo(stream, options?? new (CompressionType.Deflate));
}
}

View File

@@ -0,0 +1,29 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Writers;
namespace SharpCompress.Archives;
public static class IWritableAsyncArchiveExtensions
{
public static ValueTask SaveToAsync(
this IWritableAsyncArchive writableArchive,
string filePath,
WriterOptions? options = null,
CancellationToken cancellationToken = default
) => writableArchive.SaveToAsync(new FileInfo(filePath), options ?? new (CompressionType.Deflate), cancellationToken);
public static async ValueTask SaveToAsync(
this IWritableAsyncArchive writableArchive,
FileInfo fileInfo,
WriterOptions? options = null,
CancellationToken cancellationToken = default
)
{
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
await writableArchive.SaveToAsync(stream, options?? new (CompressionType.Deflate), cancellationToken).ConfigureAwait(false);
}
}

View File

@@ -1,4 +1,6 @@
using System.Linq;
using System.Threading.Tasks;
using SharpCompress.Common.Rar;
namespace SharpCompress.Archives.Rar;
@@ -7,12 +9,25 @@ public static class RarArchiveExtensions
/// <summary>
/// RarArchive is the first volume of a multi-part archive. If MultipartVolume is true and IsFirstVolume is false then the first volume file must be missing.
/// </summary>
public static bool IsFirstVolume(this RarArchive archive) =>
archive.Volumes.First().IsFirstVolume;
public static bool IsFirstVolume(this IRarArchive archive) =>
archive.Volumes.Cast<RarVolume>().First().IsFirstVolume;
/// <summary>
/// RarArchive is part of a multi-part archive.
/// </summary>
public static bool IsMultipartVolume(this RarArchive archive) =>
archive.Volumes.First().IsMultiVolume;
public static bool IsMultipartVolume(this IRarArchive archive) =>
archive.Volumes.Cast<RarVolume>().First().IsMultiVolume;
/// <summary>
/// RarArchive is the first volume of a multi-part archive. If MultipartVolume is true and IsFirstVolume is false then the first volume file must be missing.
/// </summary>
public static async ValueTask<bool> IsFirstVolumeAsync(this IRarAsyncArchive archive) =>
(await archive.VolumesAsync.CastAsync<RarVolume>().FirstAsync()).IsFirstVolume;
/// <summary>
/// RarArchive is part of a multi-part archive.
/// </summary>
public static async ValueTask<bool> IsMultipartVolumeAsync(this IRarAsyncArchive archive) =>
(await archive.VolumesAsync.CastAsync<RarVolume>().FirstAsync()).IsMultiVolume;
}

View File

@@ -14,7 +14,23 @@ using SharpCompress.Readers.Rar;
namespace SharpCompress.Archives.Rar;
public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
public interface IRarArchiveCommon
{
int MinVersion { get; }
int MaxVersion { get; }
}
public interface IRarArchive : IArchive, IRarArchiveCommon
{
}
public interface IRarAsyncArchive : IAsyncArchive, IRarArchiveCommon
{
}
public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>, IRarArchive
{
private bool _disposed;
internal Lazy<IRarUnpack> UnpackV2017 { get; } =
@@ -103,7 +119,7 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
/// </summary>
/// <param name="filePath"></param>
/// <param name="options"></param>
public static IArchive Open(string filePath, ReaderOptions? options = null)
public static IRarArchive Open(string filePath, ReaderOptions? options = null)
{
filePath.NotNullOrEmpty(nameof(filePath));
var fileInfo = new FileInfo(filePath);
@@ -121,7 +137,7 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
public static IArchive Open(FileInfo fileInfo, ReaderOptions? options = null)
public static IRarArchive Open(FileInfo fileInfo, ReaderOptions? options = null)
{
fileInfo.NotNull(nameof(fileInfo));
return new RarArchive(
@@ -138,7 +154,7 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
public static IArchive Open(Stream stream, ReaderOptions? options = null)
public static IRarArchive Open(Stream stream, ReaderOptions? options = null)
{
stream.NotNull(nameof(stream));
@@ -155,7 +171,7 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static IArchive Open(
public static IRarArchive Open(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
@@ -176,7 +192,7 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
public static IArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
public static IRarArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
{
streams.NotNull(nameof(streams));
var strms = streams.ToArray();
@@ -195,14 +211,14 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
/// <param name="cancellationToken"></param>
public static IAsyncArchive OpenAsync(
public static IRarAsyncArchive OpenAsync(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)Open(stream, readerOptions);
return (IRarAsyncArchive)Open(stream, readerOptions);
}
/// <summary>
@@ -211,14 +227,14 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
/// <param name="fileInfo"></param>
/// <param name="readerOptions"></param>
/// <param name="cancellationToken"></param>
public static IAsyncArchive OpenAsync(
public static IRarAsyncArchive OpenAsync(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)Open(fileInfo, readerOptions);
return (IRarAsyncArchive)Open(fileInfo, readerOptions);
}
/// <summary>
@@ -227,14 +243,14 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
/// <param name="cancellationToken"></param>
public static IAsyncArchive OpenAsync(
public static IRarAsyncArchive OpenAsync(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)Open(streams, readerOptions);
return (IRarAsyncArchive)Open(streams, readerOptions);
}
/// <summary>
@@ -243,14 +259,14 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
/// <param name="cancellationToken"></param>
public static IAsyncArchive OpenAsync(
public static IRarAsyncArchive OpenAsync(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)Open(fileInfos, readerOptions);
return (IRarAsyncArchive)Open(fileInfos, readerOptions);
}
public static bool IsRarFile(string filePath) => IsRarFile(new FileInfo(filePath));

View File

@@ -22,7 +22,7 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
/// </summary>
/// <param name="filePath"></param>
/// <param name="readerOptions"></param>
public static IArchive Open(string filePath, ReaderOptions? readerOptions = null)
public static IWritableArchive Open(string filePath, ReaderOptions? readerOptions = null)
{
filePath.NotNullOrEmpty(nameof(filePath));
return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
@@ -33,7 +33,7 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="readerOptions"></param>
public static IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
public static IWritableArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.NotNull(nameof(fileInfo));
return new TarArchive(
@@ -50,7 +50,7 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static IArchive Open(
public static IWritableArchive Open(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
@@ -71,7 +71,7 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
public static IArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
public static IWritableArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
{
streams.NotNull(nameof(streams));
var strms = streams.ToArray();
@@ -89,7 +89,7 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
/// </summary>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null)
public static IWritableArchive Open(Stream stream, ReaderOptions? readerOptions = null)
{
stream.NotNull(nameof(stream));
@@ -109,14 +109,24 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
/// <param name="cancellationToken"></param>
public static IAsyncArchive OpenAsync(
public static IWritableAsyncArchive OpenAsync(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)Open(stream, readerOptions);
return (IWritableAsyncArchive)Open(stream, readerOptions);
}
public static IWritableAsyncArchive OpenAsync(
string file,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)Open(new FileInfo(file), readerOptions);
}
/// <summary>
@@ -125,14 +135,14 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
/// <param name="fileInfo"></param>
/// <param name="readerOptions"></param>
/// <param name="cancellationToken"></param>
public static IAsyncArchive OpenAsync(
public static IWritableAsyncArchive OpenAsync(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)Open(fileInfo, readerOptions);
return (IWritableAsyncArchive)Open(fileInfo, readerOptions);
}
/// <summary>
@@ -141,14 +151,14 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
/// <param name="cancellationToken"></param>
public static IAsyncArchive OpenAsync(
public static IWritableAsyncArchive OpenAsync(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)Open(streams, readerOptions);
return (IWritableAsyncArchive)Open(streams, readerOptions);
}
/// <summary>
@@ -157,14 +167,14 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
/// <param name="cancellationToken"></param>
public static IAsyncArchive OpenAsync(
public static IWritableAsyncArchive OpenAsync(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)Open(fileInfos, readerOptions);
return (IWritableAsyncArchive)Open(fileInfos, readerOptions);
}
public static bool IsTarFile(string filePath) => IsTarFile(new FileInfo(filePath));

View File

@@ -43,7 +43,7 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
/// </summary>
/// <param name="filePath"></param>
/// <param name="readerOptions"></param>
public static IArchive Open(string filePath, ReaderOptions? readerOptions = null)
public static IWritableArchive Open(string filePath, ReaderOptions? readerOptions = null)
{
filePath.NotNullOrEmpty(nameof(filePath));
return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
@@ -54,7 +54,7 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="readerOptions"></param>
public static IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
public static IWritableArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.NotNull(nameof(fileInfo));
return new ZipArchive(
@@ -71,7 +71,7 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static IArchive Open(
public static IWritableArchive Open(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
@@ -92,7 +92,7 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
public static IArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
public static IWritableArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
{
streams.NotNull(nameof(streams));
var strms = streams.ToArray();
@@ -110,7 +110,7 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
/// </summary>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null)
public static IWritableArchive Open(Stream stream, ReaderOptions? readerOptions = null)
{
stream.NotNull(nameof(stream));
@@ -124,14 +124,14 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
);
}
public static IAsyncArchive OpenAsync(
public static IWritableAsyncArchive OpenAsync(
string path,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)Open(path, readerOptions);
return (IWritableAsyncArchive)Open(path, readerOptions);
}
/// <summary>
@@ -140,14 +140,14 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
/// <param name="cancellationToken"></param>
public static IAsyncArchive OpenAsync(
public static IWritableAsyncArchive OpenAsync(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)Open(stream, readerOptions);
return (IWritableAsyncArchive)Open(stream, readerOptions);
}
/// <summary>
@@ -156,14 +156,14 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
/// <param name="fileInfo"></param>
/// <param name="readerOptions"></param>
/// <param name="cancellationToken"></param>
public static IAsyncArchive OpenAsync(
public static IWritableAsyncArchive OpenAsync(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)Open(fileInfo, readerOptions);
return (IWritableAsyncArchive)Open(fileInfo, readerOptions);
}
/// <summary>
@@ -172,14 +172,14 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
/// <param name="cancellationToken"></param>
public static IAsyncArchive OpenAsync(
public static IWritableAsyncArchive OpenAsync(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)Open(streams, readerOptions);
return (IWritableAsyncArchive)Open(streams, readerOptions);
}
/// <summary>
@@ -188,14 +188,14 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
/// <param name="cancellationToken"></param>
public static IAsyncArchive OpenAsync(
public static IWritableAsyncArchive OpenAsync(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)Open(fileInfos, readerOptions);
return (IWritableAsyncArchive)Open(fileInfos, readerOptions);
}
public static bool IsZipFile(

View File

@@ -116,6 +116,24 @@ public static class AsyncEnumerableExtensions
}
}
public static async ValueTask<T> SingleAsync<T>(
this IAsyncEnumerable<T> source,
Func<T, bool> predicate
)
{
var enumerator = source.WhereAsync(predicate).GetAsyncEnumerator();
if (!await enumerator.MoveNextAsync())
{
throw new InvalidOperationException("The source sequence is empty.");
}
var value = enumerator.Current;
if (await enumerator.MoveNextAsync())
{
throw new InvalidOperationException("The source sequence contains more than one element.");
}
return value;
}
public static async ValueTask<T> FirstAsync<T>(this IAsyncEnumerable<T> source)
{
await foreach (var item in source)

View File

@@ -216,9 +216,9 @@
"net10.0": {
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "ISahzLHsHY7vrwqr2p1YWZ+gsxoBRtH7gWRDK8fDUst9pp2He0GiesaqEfeX0V8QMCJM3eNEHGGpnIcPjFo2NQ=="
"requested": "[10.0.0, )",
"resolved": "10.0.0",
"contentHash": "kICGrGYEzCNI3wPzfEXcwNHgTvlvVn9yJDhSdRK+oZQy4jvYH529u7O0xf5ocQKzOMjfS07+3z9PKRIjrFMJDA=="
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",

View File

@@ -601,7 +601,7 @@ public class ArchiveTests : ReaderTests
)
)
await using (
var archive = await archiveFactory.OpenAsync(
var archive = archiveFactory.OpenAsync(
new AsyncOnlyStream(stream),
readerOptions
)

View File

@@ -48,8 +48,7 @@ public class GZipArchiveAsyncTests : ArchiveTests
await using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")))
#endif
{
IAsyncArchive archive = await GZipArchive.OpenAsync(stream);
try
await using (var archive = GZipArchive.OpenAsync(stream))
{
var entry = await archive.EntriesAsync.FirstAsync();
await entry.WriteToFileAsync(Path.Combine(SCRATCH_FILES_PATH, entry.Key.NotNull()));
@@ -61,10 +60,6 @@ public class GZipArchiveAsyncTests : ArchiveTests
Assert.Equal(size, scratch.Length);
Assert.Equal(size, test.Length);
}
finally
{
archive.DisposeAsync().AsTask().Wait();
}
}
CompareArchivesByPath(
Path.Combine(SCRATCH_FILES_PATH, "Tar.tar"),
@@ -81,18 +76,12 @@ public class GZipArchiveAsyncTests : ArchiveTests
#else
await using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"));
#endif
IAsyncArchive archive = await GZipArchive.OpenAsync(stream);
try
await using (var archive = GZipArchive.OpenAsync(stream))
{
await Assert.ThrowsAsync<InvalidFormatException>(() =>
archive.AddEntry("jpg\\test.jpg", jpg)
);
Assert.Throws<InvalidFormatException>(() =>
archive.AddEntry("jpg\\test.jpg", jpg));
await archive.SaveToAsync(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz"));
}
finally
{
await archive.DisposeAsync();
}
}
[Fact]

View File

@@ -36,59 +36,61 @@ public class RarReaderAsyncTests : ReaderTests
"Rar5.multi.part06.rar",
]);
private async ValueTask DoRar_Multi_Reader_Async(string[] archives)
{
using (
var reader = RarReader.Open(
archives
.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s))
.Select(p => File.OpenRead(p))
)
)
{
while (await reader.MoveToNextEntryAsync())
{
await reader.WriteEntryToDirectoryAsync(
SCRATCH_FILES_PATH,
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
);
}
}
VerifyFiles();
}
private async ValueTask DoRar_Multi_Reader_Async(string[] archives)
{
using (
IReader baseReader = RarReader.Open(
archives
.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s))
.Select(p => File.OpenRead(p))
)
)
{
IAsyncReader reader = (IAsyncReader)baseReader;
while (await reader.MoveToNextEntryAsync())
{
await reader.WriteEntryToDirectoryAsync(
SCRATCH_FILES_PATH,
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
);
}
}
VerifyFiles();
}
[Fact]
public async ValueTask Rar_Multi_Reader_Encrypted_Async() =>
await Assert.ThrowsAsync<InvalidFormatException>(async () =>
{
string[] archives =
[
"Rar.EncryptedParts.part01.rar",
"Rar.EncryptedParts.part02.rar",
"Rar.EncryptedParts.part03.rar",
"Rar.EncryptedParts.part04.rar",
"Rar.EncryptedParts.part05.rar",
"Rar.EncryptedParts.part06.rar",
];
using (
var reader = RarReader.Open(
archives
.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s))
.Select(p => File.OpenRead(p)),
new ReaderOptions { Password = "test" }
)
)
{
while (await reader.MoveToNextEntryAsync())
{
await reader.WriteEntryToDirectoryAsync(
SCRATCH_FILES_PATH,
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
);
}
}
VerifyFiles();
});
[Fact]
public async ValueTask Rar_Multi_Reader_Encrypted_Async() =>
await Assert.ThrowsAsync<InvalidFormatException>(async () =>
{
string[] archives =
[
"Rar.EncryptedParts.part01.rar",
"Rar.EncryptedParts.part02.rar",
"Rar.EncryptedParts.part03.rar",
"Rar.EncryptedParts.part04.rar",
"Rar.EncryptedParts.part05.rar",
"Rar.EncryptedParts.part06.rar",
];
using (
IReader baseReader = RarReader.Open(
archives
.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s))
.Select(p => File.OpenRead(p)),
new ReaderOptions { Password = "test" }
)
)
{
IAsyncReader reader = (IAsyncReader)baseReader;
while (await reader.MoveToNextEntryAsync())
{
await reader.WriteEntryToDirectoryAsync(
SCRATCH_FILES_PATH,
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
);
}
}
VerifyFiles();
});
[Fact]
public async ValueTask Rar_Multi_Reader_Delete_Files_Async() =>
@@ -121,30 +123,31 @@ public class RarReaderAsyncTests : ReaderTests
Path.Combine(SCRATCH2_FILES_PATH, file)
);
}
var streams = archives
.Select(s => Path.Combine(SCRATCH2_FILES_PATH, s))
.Select(File.OpenRead)
.ToList();
using (var reader = RarReader.Open(streams))
{
while (await reader.MoveToNextEntryAsync())
{
await reader.WriteEntryToDirectoryAsync(
SCRATCH_FILES_PATH,
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
);
}
}
foreach (var stream in streams)
{
stream.Dispose();
}
VerifyFiles();
var streams = archives
.Select(s => Path.Combine(SCRATCH2_FILES_PATH, s))
.Select(File.OpenRead)
.ToList();
using (IReader baseReader = RarReader.Open(streams))
{
IAsyncReader reader = (IAsyncReader)baseReader;
while (await reader.MoveToNextEntryAsync())
{
await reader.WriteEntryToDirectoryAsync(
SCRATCH_FILES_PATH,
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
);
}
}
foreach (var stream in streams)
{
stream.Dispose();
}
VerifyFiles();
foreach (var file in archives.Select(s => Path.Combine(SCRATCH2_FILES_PATH, s)))
{
File.Delete(file);
}
foreach (var file in archives.Select(s => Path.Combine(SCRATCH2_FILES_PATH, s)))
{
File.Delete(file);
}
}
[Fact]
@@ -271,23 +274,24 @@ public class RarReaderAsyncTests : ReaderTests
);
}
[Fact]
public async ValueTask Rar_Jpg_Reader_Async()
{
using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg")))
using (var reader = RarReader.Open(stream, new ReaderOptions { LookForHeader = true }))
{
while (await reader.MoveToNextEntryAsync())
{
Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType);
await reader.WriteEntryToDirectoryAsync(
SCRATCH_FILES_PATH,
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
);
}
}
VerifyFiles();
}
[Fact]
public async ValueTask Rar_Jpg_Reader_Async()
{
using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg")))
using (IReader baseReader = RarReader.Open(stream, new ReaderOptions { LookForHeader = true }))
{
IAsyncReader reader = (IAsyncReader)baseReader;
while (await reader.MoveToNextEntryAsync())
{
Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType);
await reader.WriteEntryToDirectoryAsync(
SCRATCH_FILES_PATH,
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
);
}
}
VerifyFiles();
}
[Fact]
public async ValueTask Rar_Solid_Reader_Async() =>

View File

@@ -47,7 +47,7 @@ public class TarArchiveAsyncTests : ArchiveTests
var unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive);
using (var archive2 = TarArchive.Open(unmodified))
{
Assert.Equal(1, archive2.Entries.Count);
Assert.Equal(1, archive2.Entries.Count());
Assert.Contains(filename, archive2.Entries.Select(entry => entry.Key));
foreach (var entry in archive2.Entries)
@@ -91,7 +91,7 @@ public class TarArchiveAsyncTests : ArchiveTests
var unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive);
using (var archive2 = TarArchive.Open(unmodified))
{
Assert.Equal(1, archive2.Entries.Count);
Assert.Equal(1, archive2.Entries.Count());
Assert.Contains(longFilename, archive2.Entries.Select(entry => entry.Key));
foreach (var entry in archive2.Entries)
@@ -128,7 +128,7 @@ public class TarArchiveAsyncTests : ArchiveTests
var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.mod.tar");
var modified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar");
using (var archive = TarArchive.Open(unmodified))
await using (var archive = TarArchive.OpenAsync(unmodified))
{
archive.AddEntry("jpg\\test.jpg", jpg);
await archive.SaveToAsync(scratchPath, new WriterOptions(CompressionType.None));
@@ -143,9 +143,9 @@ public class TarArchiveAsyncTests : ArchiveTests
var modified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.mod.tar");
var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar");
using (var archive = TarArchive.Open(unmodified))
await using (var archive = TarArchive.OpenAsync(unmodified))
{
var entry = archive.Entries.Single(x =>
var entry = await archive.EntriesAsync.SingleAsync(x =>
x.Key.NotNull().EndsWith("jpg", StringComparison.OrdinalIgnoreCase)
);
archive.RemoveEntry(entry);

View File

@@ -49,7 +49,7 @@ public class TarArchiveTests : ArchiveTests
var unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive);
using (var archive2 = TarArchive.Open(unmodified))
{
Assert.Equal(1, archive2.Entries.Count);
Assert.Equal(1, archive2.Entries.Count());
Assert.Contains(filename, archive2.Entries.Select(entry => entry.Key));
foreach (var entry in archive2.Entries)
@@ -67,7 +67,7 @@ public class TarArchiveTests : ArchiveTests
{
var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "very long filename.tar");
using var archive = TarArchive.Open(unmodified);
Assert.Equal(5, archive.Entries.Count);
Assert.Equal(5, archive.Entries.Count());
Assert.Contains("very long filename/", archive.Entries.Select(entry => entry.Key));
Assert.Contains(
"very long filename/very long filename very long filename very long filename very long filename very long filename very long filename very long filename very long filename very long filename very long filename.jpg",
@@ -109,7 +109,7 @@ public class TarArchiveTests : ArchiveTests
var unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive);
using (var archive2 = TarArchive.Open(unmodified))
{
Assert.Equal(1, archive2.Entries.Count);
Assert.Equal(1, archive2.Entries.Count());
Assert.Contains(longFilename, archive2.Entries.Select(entry => entry.Key));
foreach (var entry in archive2.Entries)
@@ -127,7 +127,7 @@ public class TarArchiveTests : ArchiveTests
{
var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "ustar with long names.tar");
using var archive = TarArchive.Open(unmodified);
Assert.Equal(6, archive.Entries.Count);
Assert.Equal(6, archive.Entries.Count());
Assert.Contains("Directory/", archive.Entries.Select(entry => entry.Key));
Assert.Contains(
"Directory/Some file with veeeeeeeeeery loooooooooong name",

View File

@@ -126,10 +126,9 @@ public class ZipArchiveAsyncTests : ArchiveTests
var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip");
var modified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod.zip");
using (IArchive baseArchive = ZipArchive.Open(unmodified))
await using (var archive = ZipArchive.OpenAsync(unmodified))
{
IWritableArchive archive = (IWritableArchive)baseArchive;
var entry = archive.Entries.Single(x =>
var entry = await archive.EntriesAsync.SingleAsync(x =>
x.Key.NotNull().EndsWith("jpg", StringComparison.OrdinalIgnoreCase)
);
archive.RemoveEntry(entry);
@@ -150,9 +149,8 @@ public class ZipArchiveAsyncTests : ArchiveTests
var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod.zip");
var modified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip");
using (IArchive baseArchive = ZipArchive.Open(unmodified))
await using (var archive = ZipArchive.OpenAsync(unmodified))
{
IWritableArchive archive = (IWritableArchive)baseArchive;
archive.AddEntry("jpg\\test.jpg", jpg);
WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate);

View File

@@ -495,7 +495,7 @@ public class ZipArchiveTests : ArchiveTests
new ReaderOptions { Password = "test" }
);
var isComplete = reader.IsComplete;
Assert.Equal(1, reader.Volumes.Count);
Assert.Equal(1, reader.Volumes.Count());
var expectedComment =
"Encoding:utf-8 || Compression:Deflate levelDefault || Encrypt:None || ZIP64:Always\r\nCreated at 2017-Jan-23 14:10:43 || DotNetZip Tool v1.9.1.8\r\nTest zip64 archive";
@@ -708,7 +708,7 @@ public class ZipArchiveTests : ArchiveTests
var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.LongComment.zip");
using var za = ZipArchive.Open(zipPath);
var count = za.Entries.Count;
var count = za.Entries.Count();
Assert.Equal(1, count);
}

View File

@@ -121,8 +121,9 @@ public class ZipReaderAsyncTests : ReaderTests
using (
Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.pkware.zip"))
)
using (var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" }))
using (IReader baseReader = ZipReader.Open(stream, new ReaderOptions { Password = "test" }))
{
IAsyncReader reader = (IAsyncReader)baseReader;
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)
@@ -191,8 +192,11 @@ public class ZipReaderAsyncTests : ReaderTests
File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.WinzipAES.zip"))
)
)
using (var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" }))
using (
IReader baseReader = ZipReader.Open(stream, new ReaderOptions { Password = "test" })
)
{
IAsyncReader reader = (IAsyncReader)baseReader;
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)
@@ -216,8 +220,9 @@ public class ZipReaderAsyncTests : ReaderTests
File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES.zip"))
)
)
using (var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" }))
using (IReader baseReader = ZipReader.Open(stream, new ReaderOptions { Password = "test" }))
{
IAsyncReader reader = (IAsyncReader)baseReader;
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)
@@ -242,8 +247,9 @@ public class ZipReaderAsyncTests : ReaderTests
File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "zipcrypto.zip"))
)
)
using (var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" }))
using (IReader baseReader = ZipReader.Open(stream, new ReaderOptions { Password = "test" }))
{
IAsyncReader reader = (IAsyncReader)baseReader;
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)