mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-25 08:24:58 +00:00
Integrate async/await support from PR #976 as baseline
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.GZip;
|
||||
|
||||
namespace SharpCompress.Archives.GZip;
|
||||
@@ -20,6 +22,12 @@ public class GZipArchiveEntry : GZipEntry, IArchiveEntry
|
||||
return Parts.Single().GetCompressedStream().NotNull();
|
||||
}
|
||||
|
||||
public virtual Task<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// GZip synchronous implementation is fast enough, just wrap it
|
||||
return Task.FromResult(OpenEntryStream());
|
||||
}
|
||||
|
||||
#region IArchiveEntry Members
|
||||
|
||||
public IArchive Archive { get; }
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
@@ -11,6 +13,12 @@ public interface IArchiveEntry : IEntry
|
||||
/// </summary>
|
||||
Stream OpenEntryStream();
|
||||
|
||||
/// <summary>
|
||||
/// Opens the current entry as a stream that will decompress as it is read asynchronously.
|
||||
/// Read the entire stream or use SkipEntry on EntryStream.
|
||||
/// </summary>
|
||||
Task<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// The archive can find all the parts of the archive needed to extract this entry.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,6 +2,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Rar;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
@@ -84,6 +86,9 @@ public class RarArchiveEntry : RarEntry, IArchiveEntry
|
||||
);
|
||||
}
|
||||
|
||||
public Task<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default) =>
|
||||
Task.FromResult(OpenEntryStream());
|
||||
|
||||
public bool IsComplete
|
||||
{
|
||||
get
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.SevenZip;
|
||||
|
||||
namespace SharpCompress.Archives.SevenZip;
|
||||
@@ -10,6 +12,9 @@ public class SevenZipArchiveEntry : SevenZipEntry, IArchiveEntry
|
||||
|
||||
public Stream OpenEntryStream() => FilePart.GetCompressedStream();
|
||||
|
||||
public Task<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default) =>
|
||||
Task.FromResult(OpenEntryStream());
|
||||
|
||||
public IArchive Archive { get; }
|
||||
|
||||
public bool IsComplete => true;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Tar;
|
||||
|
||||
@@ -12,6 +14,9 @@ public class TarArchiveEntry : TarEntry, IArchiveEntry
|
||||
|
||||
public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream().NotNull();
|
||||
|
||||
public virtual Task<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default) =>
|
||||
Task.FromResult(OpenEntryStream());
|
||||
|
||||
#region IArchiveEntry Members
|
||||
|
||||
public IArchive Archive { get; }
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Zip;
|
||||
|
||||
namespace SharpCompress.Archives.Zip;
|
||||
@@ -11,6 +13,9 @@ public class ZipArchiveEntry : ZipEntry, IArchiveEntry
|
||||
|
||||
public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream().NotNull();
|
||||
|
||||
public virtual Task<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default) =>
|
||||
Task.FromResult(OpenEntryStream());
|
||||
|
||||
#region IArchiveEntry Members
|
||||
|
||||
public IArchive Archive { get; }
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common;
|
||||
|
||||
@@ -116,4 +118,115 @@ internal static class ExtractionMethods
|
||||
entry.PreserveExtractionOptions(destinationFileName, options);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task WriteEntryToDirectoryAsync(
|
||||
IEntry entry,
|
||||
string destinationDirectory,
|
||||
ExtractionOptions? options,
|
||||
Func<string, ExtractionOptions?, Task> writeAsync,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
string destinationFileName;
|
||||
var fullDestinationDirectoryPath = Path.GetFullPath(destinationDirectory);
|
||||
|
||||
//check for trailing slash.
|
||||
if (
|
||||
fullDestinationDirectoryPath[fullDestinationDirectoryPath.Length - 1]
|
||||
!= Path.DirectorySeparatorChar
|
||||
)
|
||||
{
|
||||
fullDestinationDirectoryPath += Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(fullDestinationDirectoryPath))
|
||||
{
|
||||
throw new ExtractionException(
|
||||
$"Directory does not exist to extract to: {fullDestinationDirectoryPath}"
|
||||
);
|
||||
}
|
||||
|
||||
options ??= new ExtractionOptions() { Overwrite = true };
|
||||
|
||||
var file = Path.GetFileName(entry.Key.NotNull("Entry Key is null")).NotNull("File is null");
|
||||
file = Utility.ReplaceInvalidFileNameChars(file);
|
||||
if (options.ExtractFullPath)
|
||||
{
|
||||
var folder = Path.GetDirectoryName(entry.Key.NotNull("Entry Key is null"))
|
||||
.NotNull("Directory is null");
|
||||
var destdir = Path.GetFullPath(Path.Combine(fullDestinationDirectoryPath, folder));
|
||||
|
||||
if (!Directory.Exists(destdir))
|
||||
{
|
||||
if (!destdir.StartsWith(fullDestinationDirectoryPath, StringComparison.Ordinal))
|
||||
{
|
||||
throw new ExtractionException(
|
||||
"Entry is trying to create a directory outside of the destination directory."
|
||||
);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(destdir);
|
||||
}
|
||||
destinationFileName = Path.Combine(destdir, file);
|
||||
}
|
||||
else
|
||||
{
|
||||
destinationFileName = Path.Combine(fullDestinationDirectoryPath, file);
|
||||
}
|
||||
|
||||
if (!entry.IsDirectory)
|
||||
{
|
||||
destinationFileName = Path.GetFullPath(destinationFileName);
|
||||
|
||||
if (
|
||||
!destinationFileName.StartsWith(
|
||||
fullDestinationDirectoryPath,
|
||||
StringComparison.Ordinal
|
||||
)
|
||||
)
|
||||
{
|
||||
throw new ExtractionException(
|
||||
"Entry is trying to write a file outside of the destination directory."
|
||||
);
|
||||
}
|
||||
await writeAsync(destinationFileName, options).ConfigureAwait(false);
|
||||
}
|
||||
else if (options.ExtractFullPath && !Directory.Exists(destinationFileName))
|
||||
{
|
||||
Directory.CreateDirectory(destinationFileName);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task WriteEntryToFileAsync(
|
||||
IEntry entry,
|
||||
string destinationFileName,
|
||||
ExtractionOptions? options,
|
||||
Func<string, FileMode, Task> openAndWriteAsync,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (entry.LinkTarget != null)
|
||||
{
|
||||
if (options?.WriteSymbolicLink is null)
|
||||
{
|
||||
throw new ExtractionException(
|
||||
"Entry is a symbolic link but ExtractionOptions.WriteSymbolicLink delegate is null"
|
||||
);
|
||||
}
|
||||
options.WriteSymbolicLink(destinationFileName, entry.LinkTarget);
|
||||
}
|
||||
else
|
||||
{
|
||||
var fm = FileMode.Create;
|
||||
options ??= new ExtractionOptions() { Overwrite = true };
|
||||
|
||||
if (!options.Overwrite)
|
||||
{
|
||||
fm = FileMode.CreateNew;
|
||||
}
|
||||
|
||||
await openAndWriteAsync(destinationFileName, fm).ConfigureAwait(false);
|
||||
entry.PreserveExtractionOptions(destinationFileName, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.IO;
|
||||
|
||||
@@ -326,20 +327,146 @@ public class SharpCompressStream : Stream, IStreamStack
|
||||
_internalPosition += count;
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
if (count == 0)
|
||||
return 0;
|
||||
|
||||
if (_bufferingEnabled)
|
||||
{
|
||||
ValidateBufferState();
|
||||
|
||||
// Fill buffer if needed
|
||||
if (_bufferedLength == 0)
|
||||
{
|
||||
_bufferedLength = await Stream
|
||||
.ReadAsync(_buffer!, 0, _bufferSize, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
_bufferPosition = 0;
|
||||
}
|
||||
int available = _bufferedLength - _bufferPosition;
|
||||
int toRead = Math.Min(count, available);
|
||||
if (toRead > 0)
|
||||
{
|
||||
Array.Copy(_buffer!, _bufferPosition, buffer, offset, toRead);
|
||||
_bufferPosition += toRead;
|
||||
_internalPosition += toRead;
|
||||
return toRead;
|
||||
}
|
||||
// If buffer exhausted, refill
|
||||
int r = await Stream
|
||||
.ReadAsync(_buffer!, 0, _bufferSize, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (r == 0)
|
||||
return 0;
|
||||
_bufferedLength = r;
|
||||
_bufferPosition = 0;
|
||||
if (_bufferedLength == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
toRead = Math.Min(count, _bufferedLength);
|
||||
Array.Copy(_buffer!, 0, buffer, offset, toRead);
|
||||
_bufferPosition = toRead;
|
||||
_internalPosition += toRead;
|
||||
return toRead;
|
||||
}
|
||||
else
|
||||
{
|
||||
int read = await Stream
|
||||
.ReadAsync(buffer, offset, count, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
_internalPosition += read;
|
||||
return read;
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task WriteAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
await Stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||
_internalPosition += count;
|
||||
}
|
||||
|
||||
public override async Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await Stream.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
#if !NETFRAMEWORK && !NETSTANDARD2_0
|
||||
|
||||
//public override int Read(Span<byte> buffer)
|
||||
//{
|
||||
// int bytesRead = Stream.Read(buffer);
|
||||
// _internalPosition += bytesRead;
|
||||
// return bytesRead;
|
||||
//}
|
||||
public override async ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (buffer.Length == 0)
|
||||
return 0;
|
||||
|
||||
// public override void Write(ReadOnlySpan<byte> buffer)
|
||||
// {
|
||||
// Stream.Write(buffer);
|
||||
// _internalPosition += buffer.Length;
|
||||
// }
|
||||
if (_bufferingEnabled)
|
||||
{
|
||||
ValidateBufferState();
|
||||
|
||||
// Fill buffer if needed
|
||||
if (_bufferedLength == 0)
|
||||
{
|
||||
_bufferedLength = await Stream
|
||||
.ReadAsync(_buffer.AsMemory(0, _bufferSize), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
_bufferPosition = 0;
|
||||
}
|
||||
int available = _bufferedLength - _bufferPosition;
|
||||
int toRead = Math.Min(buffer.Length, available);
|
||||
if (toRead > 0)
|
||||
{
|
||||
_buffer.AsSpan(_bufferPosition, toRead).CopyTo(buffer.Span);
|
||||
_bufferPosition += toRead;
|
||||
_internalPosition += toRead;
|
||||
return toRead;
|
||||
}
|
||||
// If buffer exhausted, refill
|
||||
int r = await Stream
|
||||
.ReadAsync(_buffer.AsMemory(0, _bufferSize), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (r == 0)
|
||||
return 0;
|
||||
_bufferedLength = r;
|
||||
_bufferPosition = 0;
|
||||
if (_bufferedLength == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
toRead = Math.Min(buffer.Length, _bufferedLength);
|
||||
_buffer.AsSpan(0, toRead).CopyTo(buffer.Span);
|
||||
_bufferPosition = toRead;
|
||||
_internalPosition += toRead;
|
||||
return toRead;
|
||||
}
|
||||
else
|
||||
{
|
||||
int read = await Stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
_internalPosition += read;
|
||||
return read;
|
||||
}
|
||||
}
|
||||
|
||||
public override async ValueTask WriteAsync(
|
||||
ReadOnlyMemory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
await Stream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
_internalPosition += buffer.Length;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress.IO;
|
||||
@@ -238,6 +240,106 @@ public class SourceStream : Stream, IStreamStack
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public override async Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var total = count;
|
||||
var r = -1;
|
||||
|
||||
while (count != 0 && r != 0)
|
||||
{
|
||||
r = await Current
|
||||
.ReadAsync(
|
||||
buffer,
|
||||
offset,
|
||||
(int)Math.Min(count, Current.Length - Current.Position),
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
count -= r;
|
||||
offset += r;
|
||||
|
||||
if (!IsVolumes && count != 0 && Current.Position == Current.Length)
|
||||
{
|
||||
var length = Current.Length;
|
||||
|
||||
// Load next file if present
|
||||
if (!SetStream(_stream + 1))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Current stream switched
|
||||
// Add length of previous stream
|
||||
_prevSize += length;
|
||||
Current.Seek(0, SeekOrigin.Begin);
|
||||
r = -1; //BugFix: reset to allow loop if count is still not 0 - was breaking split zipx (lzma xz etc)
|
||||
}
|
||||
}
|
||||
|
||||
return total - count;
|
||||
}
|
||||
|
||||
#if !NETFRAMEWORK && !NETSTANDARD2_0
|
||||
|
||||
public override async ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (buffer.Length <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var total = buffer.Length;
|
||||
var count = buffer.Length;
|
||||
var offset = 0;
|
||||
var r = -1;
|
||||
|
||||
while (count != 0 && r != 0)
|
||||
{
|
||||
r = await Current
|
||||
.ReadAsync(
|
||||
buffer.Slice(offset, (int)Math.Min(count, Current.Length - Current.Position)),
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
count -= r;
|
||||
offset += r;
|
||||
|
||||
if (!IsVolumes && count != 0 && Current.Position == Current.Length)
|
||||
{
|
||||
var length = Current.Length;
|
||||
|
||||
// Load next file if present
|
||||
if (!SetStream(_stream + 1))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Current stream switched
|
||||
// Add length of previous stream
|
||||
_prevSize += length;
|
||||
Current.Seek(0, SeekOrigin.Begin);
|
||||
r = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return total - count;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
if (IsFileMode || !ReaderOptions.LeaveStreamOpen) //close if file mode or options specify it
|
||||
|
||||
@@ -2,6 +2,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Readers;
|
||||
@@ -171,6 +173,33 @@ public abstract class AbstractReader<TEntry, TVolume> : IReader, IReaderExtracti
|
||||
_wroteCurrentEntry = true;
|
||||
}
|
||||
|
||||
public async Task WriteEntryToAsync(
|
||||
Stream writableStream,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (_wroteCurrentEntry)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"WriteEntryToAsync or OpenEntryStream can only be called once."
|
||||
);
|
||||
}
|
||||
|
||||
if (writableStream is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writableStream));
|
||||
}
|
||||
if (!writableStream.CanWrite)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"A writable Stream was required. Use Cancel if that was intended."
|
||||
);
|
||||
}
|
||||
|
||||
await WriteAsync(writableStream, cancellationToken).ConfigureAwait(false);
|
||||
_wroteCurrentEntry = true;
|
||||
}
|
||||
|
||||
internal void Write(Stream writeStream)
|
||||
{
|
||||
var streamListener = this as IReaderExtractionListener;
|
||||
@@ -178,6 +207,15 @@ public abstract class AbstractReader<TEntry, TVolume> : IReader, IReaderExtracti
|
||||
s.TransferTo(writeStream, Entry, streamListener);
|
||||
}
|
||||
|
||||
internal async Task WriteAsync(Stream writeStream, CancellationToken cancellationToken)
|
||||
{
|
||||
var streamListener = this as IReaderExtractionListener;
|
||||
using Stream s = OpenEntryStream();
|
||||
await s
|
||||
.TransferToAsync(writeStream, Entry, streamListener, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public EntryStream OpenEntryStream()
|
||||
{
|
||||
if (_wroteCurrentEntry)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Readers;
|
||||
@@ -21,6 +23,13 @@ public interface IReader : IDisposable
|
||||
/// <param name="writableStream"></param>
|
||||
void WriteEntryTo(Stream writableStream);
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses the current entry to the stream asynchronously. This cannot be called twice for the current entry.
|
||||
/// </summary>
|
||||
/// <param name="writableStream"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
Task WriteEntryToAsync(Stream writableStream, CancellationToken cancellationToken = default);
|
||||
|
||||
bool Cancelled { get; }
|
||||
void Cancel();
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Readers;
|
||||
@@ -65,4 +67,64 @@ public static class IReaderExtensions
|
||||
reader.WriteEntryTo(fs);
|
||||
}
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Extract to specific directory asynchronously, retaining filename
|
||||
/// </summary>
|
||||
public static async Task WriteEntryToDirectoryAsync(
|
||||
this IReader reader,
|
||||
string destinationDirectory,
|
||||
ExtractionOptions? options = null,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
await ExtractionMethods
|
||||
.WriteEntryToDirectoryAsync(
|
||||
reader.Entry,
|
||||
destinationDirectory,
|
||||
options,
|
||||
(fileName, opts) => reader.WriteEntryToFileAsync(fileName, opts, cancellationToken),
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Extract to specific file asynchronously
|
||||
/// </summary>
|
||||
public static async Task WriteEntryToFileAsync(
|
||||
this IReader reader,
|
||||
string destinationFileName,
|
||||
ExtractionOptions? options = null,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
await ExtractionMethods
|
||||
.WriteEntryToFileAsync(
|
||||
reader.Entry,
|
||||
destinationFileName,
|
||||
options,
|
||||
async (x, fm) =>
|
||||
{
|
||||
using var fs = File.Open(destinationFileName, fm);
|
||||
await reader.WriteEntryToAsync(fs, cancellationToken).ConfigureAwait(false);
|
||||
},
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Extract all remaining unread entries to specific directory asynchronously, retaining filename
|
||||
/// </summary>
|
||||
public static async Task WriteAllToDirectoryAsync(
|
||||
this IReader reader,
|
||||
string destinationDirectory,
|
||||
ExtractionOptions? options = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
while (reader.MoveToNextEntry())
|
||||
{
|
||||
await reader
|
||||
.WriteEntryToDirectoryAsync(destinationDirectory, options, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress;
|
||||
@@ -217,6 +219,89 @@ internal static class Utility
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<long> TransferToAsync(
|
||||
this Stream source,
|
||||
Stream destination,
|
||||
long maxLength,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var array = ArrayPool<byte>.Shared.Rent(TEMP_BUFFER_SIZE);
|
||||
try
|
||||
{
|
||||
var maxReadSize = array.Length;
|
||||
long total = 0;
|
||||
var remaining = maxLength;
|
||||
if (remaining < maxReadSize)
|
||||
{
|
||||
maxReadSize = (int)remaining;
|
||||
}
|
||||
while (
|
||||
await ReadTransferBlockAsync(source, array, maxReadSize, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
is var (success, count)
|
||||
&& success
|
||||
)
|
||||
{
|
||||
await destination
|
||||
.WriteAsync(array, 0, count, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
total += count;
|
||||
if (remaining - count < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
remaining -= count;
|
||||
if (remaining < maxReadSize)
|
||||
{
|
||||
maxReadSize = (int)remaining;
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(array);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<long> TransferToAsync(
|
||||
this Stream source,
|
||||
Stream destination,
|
||||
Common.Entry entry,
|
||||
IReaderExtractionListener readerExtractionListener,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var array = ArrayPool<byte>.Shared.Rent(TEMP_BUFFER_SIZE);
|
||||
try
|
||||
{
|
||||
var iterations = 0;
|
||||
long total = 0;
|
||||
int count;
|
||||
while (
|
||||
(
|
||||
count = await source
|
||||
.ReadAsync(array, 0, array.Length, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
) != 0
|
||||
)
|
||||
{
|
||||
total += count;
|
||||
await destination
|
||||
.WriteAsync(array, 0, count, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
iterations++;
|
||||
readerExtractionListener.FireEntryExtractionProgress(entry, total, iterations);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(array);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ReadTransferBlock(Stream source, byte[] array, int maxSize, out int count)
|
||||
{
|
||||
var size = maxSize;
|
||||
@@ -228,6 +313,56 @@ internal static class Utility
|
||||
return count != 0;
|
||||
}
|
||||
|
||||
private static async Task<(bool success, int count)> ReadTransferBlockAsync(
|
||||
Stream source,
|
||||
byte[] array,
|
||||
int maxSize,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var size = maxSize;
|
||||
if (maxSize > array.Length)
|
||||
{
|
||||
size = array.Length;
|
||||
}
|
||||
var count = await source.ReadAsync(array, 0, size, cancellationToken).ConfigureAwait(false);
|
||||
return (count != 0, count);
|
||||
}
|
||||
|
||||
public static async Task SkipAsync(
|
||||
this Stream source,
|
||||
long advanceAmount,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (source.CanSeek)
|
||||
{
|
||||
source.Position += advanceAmount;
|
||||
return;
|
||||
}
|
||||
|
||||
var array = ArrayPool<byte>.Shared.Rent(TEMP_BUFFER_SIZE);
|
||||
try
|
||||
{
|
||||
while (advanceAmount > 0)
|
||||
{
|
||||
var toRead = (int)Math.Min(array.Length, advanceAmount);
|
||||
var read = await source
|
||||
.ReadAsync(array, 0, toRead, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (read <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
advanceAmount -= read;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(array);
|
||||
}
|
||||
}
|
||||
|
||||
#if NET60_OR_GREATER
|
||||
|
||||
public static bool ReadFully(this Stream stream, byte[] buffer)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Writers;
|
||||
@@ -22,6 +24,19 @@ public abstract class AbstractWriter(ArchiveType type, WriterOptions writerOptio
|
||||
|
||||
public abstract void Write(string filename, Stream source, DateTime? modificationTime);
|
||||
|
||||
public virtual async Task WriteAsync(
|
||||
string filename,
|
||||
Stream source,
|
||||
DateTime? modificationTime,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
// Default implementation calls synchronous version
|
||||
// Derived classes should override for true async behavior
|
||||
Write(filename, source, modificationTime);
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool isDisposing)
|
||||
{
|
||||
if (isDisposing)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Writers;
|
||||
@@ -8,4 +10,10 @@ public interface IWriter : IDisposable
|
||||
{
|
||||
ArchiveType WriterType { get; }
|
||||
void Write(string filename, Stream source, DateTime? modificationTime);
|
||||
Task WriteAsync(
|
||||
string filename,
|
||||
Stream source,
|
||||
DateTime? modificationTime,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Writers;
|
||||
|
||||
@@ -52,4 +54,71 @@ public static class IWriterExtensions
|
||||
writer.Write(file.Substring(directory.Length), file);
|
||||
}
|
||||
}
|
||||
|
||||
// Async extensions
|
||||
public static Task WriteAsync(
|
||||
this IWriter writer,
|
||||
string entryPath,
|
||||
Stream source,
|
||||
CancellationToken cancellationToken = default
|
||||
) => writer.WriteAsync(entryPath, source, null, cancellationToken);
|
||||
|
||||
public static async Task WriteAsync(
|
||||
this IWriter writer,
|
||||
string entryPath,
|
||||
FileInfo source,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (!source.Exists)
|
||||
{
|
||||
throw new ArgumentException("Source does not exist: " + source.FullName);
|
||||
}
|
||||
using var stream = source.OpenRead();
|
||||
await writer
|
||||
.WriteAsync(entryPath, stream, source.LastWriteTime, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static Task WriteAsync(
|
||||
this IWriter writer,
|
||||
string entryPath,
|
||||
string source,
|
||||
CancellationToken cancellationToken = default
|
||||
) => writer.WriteAsync(entryPath, new FileInfo(source), cancellationToken);
|
||||
|
||||
public static Task WriteAllAsync(
|
||||
this IWriter writer,
|
||||
string directory,
|
||||
string searchPattern = "*",
|
||||
SearchOption option = SearchOption.TopDirectoryOnly,
|
||||
CancellationToken cancellationToken = default
|
||||
) => writer.WriteAllAsync(directory, searchPattern, null, option, cancellationToken);
|
||||
|
||||
public static async Task WriteAllAsync(
|
||||
this IWriter writer,
|
||||
string directory,
|
||||
string searchPattern = "*",
|
||||
Func<string, bool>? fileSearchFunc = null,
|
||||
SearchOption option = SearchOption.TopDirectoryOnly,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
throw new ArgumentException("Directory does not exist: " + directory);
|
||||
}
|
||||
|
||||
fileSearchFunc ??= n => true;
|
||||
foreach (
|
||||
var file in Directory
|
||||
.EnumerateFiles(directory, searchPattern, option)
|
||||
.Where(fileSearchFunc)
|
||||
)
|
||||
{
|
||||
await writer
|
||||
.WriteAsync(file.Substring(directory.Length), file, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,9 +335,9 @@
|
||||
"net8.0": {
|
||||
"Microsoft.NET.ILLink.Tasks": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.21, )",
|
||||
"resolved": "8.0.21",
|
||||
"contentHash": "s8H5PZQs50OcNkaB6Si54+v3GWM7vzs6vxFRMlD3aXsbM+aPCtod62gmK0BYWou9diGzmo56j8cIf/PziijDqQ=="
|
||||
"requested": "[8.0.20, )",
|
||||
"resolved": "8.0.20",
|
||||
"contentHash": "Rhcto2AjGvTO62+/VTmBpumBOmqIGp7nYEbTbmEXkCq4yPGxV8whju3/HsIA/bKyo2+DggaYk5+/8sxb1AbPTw=="
|
||||
},
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
|
||||
Reference in New Issue
Block a user