Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ SharpCompress is a compression library in pure C# for .NET Framework 4.62, .NET

The major feature is support for non-seekable streams so large files can be processed on the fly (i.e. download stream).

**NEW:** All I/O operations now support async/await for improved performance and scalability. See the [Async Usage](#async-usage) section below.

GitHub Actions Build -
[![SharpCompress](https://github.com/adamhathcock/sharpcompress/actions/workflows/dotnetcore.yml/badge.svg)](https://github.com/adamhathcock/sharpcompress/actions/workflows/dotnetcore.yml)
[![Static Badge](https://img.shields.io/badge/API%20Docs-DNDocs-190088?logo=readme&logoColor=white)](https://dndocs.com/d/sharpcompress/api/index.html)
Expand Down Expand Up @@ -32,6 +34,82 @@ Hi everyone. I hope you're using SharpCompress and finding it useful. Please giv

Please do not email me directly to ask for help. If you think there is a real issue, please report it here.

## Async Usage

SharpCompress now provides full async/await support for all I/O operations, allowing for better performance and scalability in modern applications.

### Async Reading Examples

Extract entries asynchronously:
```csharp
using (Stream stream = File.OpenRead("archive.zip"))
using (var reader = ReaderFactory.Open(stream))
{
while (reader.MoveToNextEntry())
{
if (!reader.Entry.IsDirectory)
{
// Async extraction
await reader.WriteEntryToDirectoryAsync(
@"C:\temp",
new ExtractionOptions() { ExtractFullPath = true, Overwrite = true },
cancellationToken
);
}
}
}
```

Extract all entries to directory asynchronously:
```csharp
using (Stream stream = File.OpenRead("archive.tar.gz"))
using (var reader = ReaderFactory.Open(stream))
{
await reader.WriteAllToDirectoryAsync(
@"C:\temp",
new ExtractionOptions() { ExtractFullPath = true, Overwrite = true },
cancellationToken
);
}
```

Open entry stream asynchronously:
```csharp
using (var archive = ZipArchive.Open("archive.zip"))
{
foreach (var entry in archive.Entries.Where(e => !e.IsDirectory))
{
using (var entryStream = await entry.OpenEntryStreamAsync(cancellationToken))
{
// Process stream asynchronously
await entryStream.CopyToAsync(outputStream, cancellationToken);
}
}
}
```

### Async Writing Examples

Write files asynchronously:
```csharp
using (Stream stream = File.OpenWrite("output.zip"))
using (var writer = WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate))
{
await writer.WriteAsync("file1.txt", fileStream, DateTime.Now, cancellationToken);
}
```

Write all files from directory asynchronously:
```csharp
using (Stream stream = File.OpenWrite("output.tar.gz"))
using (var writer = WriterFactory.Open(stream, ArchiveType.Tar, new WriterOptions(CompressionType.GZip)))
{
await writer.WriteAllAsync(@"D:\files", "*", SearchOption.AllDirectories, cancellationToken);
}
```

All async methods support `CancellationToken` for graceful cancellation of long-running operations.

## Want to contribute?

I'm always looking for help or ideas. Please submit code or email with ideas. Unfortunately, just letting me know you'd like to help is not enough because I really have no overall plan of what needs to be done. I'll definitely accept code submissions and add you as a member of the project!
Expand Down
143 changes: 143 additions & 0 deletions USAGE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# SharpCompress Usage

## Async/Await Support

SharpCompress now provides full async/await support for all I/O operations. All `Read`, `Write`, and extraction operations have async equivalents ending in `Async` that accept an optional `CancellationToken`. This enables better performance and scalability for I/O-bound operations.

**Key Async Methods:**
- `reader.WriteEntryToAsync(stream, cancellationToken)` - Extract entry asynchronously
- `reader.WriteAllToDirectoryAsync(path, options, cancellationToken)` - Extract all asynchronously
- `writer.WriteAsync(filename, stream, modTime, cancellationToken)` - Write entry asynchronously
- `writer.WriteAllAsync(directory, pattern, searchOption, cancellationToken)` - Write directory asynchronously
- `entry.OpenEntryStreamAsync(cancellationToken)` - Open entry stream asynchronously

See [Async Examples](#async-examples) section below for usage patterns.

## Stream Rules (changed with 0.21)

When dealing with Streams, the rule should be that you don't close a stream you didn't create. This, in effect, should mean you should always put a Stream in a using block to dispose it.
Expand Down Expand Up @@ -172,3 +185,133 @@ foreach(var entry in tr.Entries)
Console.WriteLine($"{entry.Key}");
}
```

## Async Examples

### Async Reader Examples

**Extract single entry asynchronously:**
```C#
using (Stream stream = File.OpenRead("archive.zip"))
using (var reader = ReaderFactory.Open(stream))
{
while (reader.MoveToNextEntry())
{
if (!reader.Entry.IsDirectory)
{
using (var entryStream = reader.OpenEntryStream())
{
using (var outputStream = File.Create("output.bin"))
{
await reader.WriteEntryToAsync(outputStream, cancellationToken);
}
}
}
}
}
```

**Extract all entries asynchronously:**
```C#
using (Stream stream = File.OpenRead("archive.tar.gz"))
using (var reader = ReaderFactory.Open(stream))
{
await reader.WriteAllToDirectoryAsync(
@"D:\temp",
new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
},
cancellationToken
);
}
```

**Open and process entry stream asynchronously:**
```C#
using (var archive = ZipArchive.Open("archive.zip"))
{
foreach (var entry in archive.Entries.Where(e => !e.IsDirectory))
{
using (var entryStream = await entry.OpenEntryStreamAsync(cancellationToken))
{
// Process the decompressed stream asynchronously
await ProcessStreamAsync(entryStream, cancellationToken);
}
}
}
```

### Async Writer Examples

**Write single file asynchronously:**
```C#
using (Stream archiveStream = File.OpenWrite("output.zip"))
using (var writer = WriterFactory.Open(archiveStream, ArchiveType.Zip, CompressionType.Deflate))
{
using (Stream fileStream = File.OpenRead("input.txt"))
{
await writer.WriteAsync("entry.txt", fileStream, DateTime.Now, cancellationToken);
}
}
```

**Write entire directory asynchronously:**
```C#
using (Stream stream = File.OpenWrite("backup.tar.gz"))
using (var writer = WriterFactory.Open(stream, ArchiveType.Tar, new WriterOptions(CompressionType.GZip)))
{
await writer.WriteAllAsync(
@"D:\files",
"*",
SearchOption.AllDirectories,
cancellationToken
);
}
```

**Write with progress tracking and cancellation:**
```C#
var cts = new CancellationTokenSource();

// Set timeout or cancel from UI
cts.CancelAfter(TimeSpan.FromMinutes(5));

using (Stream stream = File.OpenWrite("archive.zip"))
using (var writer = WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate))
{
try
{
await writer.WriteAllAsync(@"D:\data", "*", SearchOption.AllDirectories, cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation was cancelled");
}
}
```

### Archive Async Examples

**Extract from archive asynchronously:**
```C#
using (var archive = ZipArchive.Open("archive.zip"))
{
using (var reader = archive.ExtractAllEntries())
{
await reader.WriteAllToDirectoryAsync(
@"C:\output",
new ExtractionOptions() { ExtractFullPath = true, Overwrite = true },
cancellationToken
);
}
}
```

**Benefits of Async Operations:**
- Non-blocking I/O for better application responsiveness
- Improved scalability for server applications
- Support for cancellation via CancellationToken
- Better resource utilization in async/await contexts
- Compatible with modern .NET async patterns
8 changes: 8 additions & 0 deletions src/SharpCompress/Archives/GZip/GZipArchiveEntry.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -20,6 +22,12 @@ public virtual Stream OpenEntryStream()
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; }
Expand Down
8 changes: 8 additions & 0 deletions src/SharpCompress/Archives/IArchiveEntry.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;

namespace SharpCompress.Archives;
Expand All @@ -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>
Expand Down
5 changes: 5 additions & 0 deletions src/SharpCompress/Archives/Rar/RarArchiveEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
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;
Expand Down Expand Up @@ -84,6 +86,9 @@ public Stream OpenEntryStream()
);
}

public Task<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default) =>
Task.FromResult(OpenEntryStream());

public bool IsComplete
{
get
Expand Down
5 changes: 5 additions & 0 deletions src/SharpCompress/Archives/SevenZip/SevenZipArchiveEntry.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common.SevenZip;

namespace SharpCompress.Archives.SevenZip;
Expand All @@ -10,6 +12,9 @@ internal SevenZipArchiveEntry(SevenZipArchive archive, SevenZipFilePart part)

public Stream OpenEntryStream() => FilePart.GetCompressedStream();

public Task<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default) =>
Task.FromResult(OpenEntryStream());

public IArchive Archive { get; }

public bool IsComplete => true;
Expand Down
5 changes: 5 additions & 0 deletions src/SharpCompress/Archives/Tar/TarArchiveEntry.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Tar;

Expand All @@ -12,6 +14,9 @@ internal TarArchiveEntry(TarArchive archive, TarFilePart? part, CompressionType

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; }
Expand Down
5 changes: 5 additions & 0 deletions src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -11,6 +13,9 @@ internal ZipArchiveEntry(ZipArchive archive, SeekableZipFilePart? part)

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; }
Expand Down
Loading
Loading