EonaCat.FileSplitter 1.0.0

Prefix Reserved
dotnet add package EonaCat.FileSplitter --version 1.0.0
                    
NuGet\Install-Package EonaCat.FileSplitter -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EonaCat.FileSplitter" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EonaCat.FileSplitter" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="EonaCat.FileSplitter" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EonaCat.FileSplitter --version 1.0.0
                    
#r "nuget: EonaCat.FileSplitter, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EonaCat.FileSplitter@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EonaCat.FileSplitter&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=EonaCat.FileSplitter&version=1.0.0
                    
Install as a Cake Tool

EonaCat.FileSplitter

Fast, reliable file splitting and assembly for .NET.

EonaCat.FileSplitter splits large files into manageable chunks, stores SHA-256 integrity information in manifests, optionally compresses chunks with GZip, and can assemble one or multiple files back into their original form.

Features

  • Split a single file into chunks.
  • Split multiple files into one package.
  • Assemble a single file from a package.
  • Assemble every file in a package.
  • Optional GZip compression for chunks.
  • SHA-256 hashing for every chunk and the complete file.
  • Integrity validation during assembly.
  • Progress reporting with IProgress<T>.
  • Cancellation support with CancellationToken.
  • Safe temporary-file assembly to avoid leaving a partially written destination.
  • Targets .NET Standard 2.0, making the library usable from a wide range of .NET applications.
  • No external runtime dependencies.

Installation

NuGet

dotnet add package EonaCat.FileSplitter

Basic usage

Split a file

using EonaCat.FileSplitter;
using EonaCat.FileSplitter.Models;

var splitter = new FileSplitter();

FileManifest manifest = await splitter.SplitAsync(
    sourceFile: @"C:\Files\large-file.zip",
    outputDirectory: @"C:\Packages\large-file");

Console.WriteLine($"Created {manifest.Chunks.Count} chunks.");
Console.WriteLine($"SHA-256: {manifest.OverallSha256}");

The package directory contains a manifest.json and a chunks directory:

large-file/
├── manifest.json
├── index.json
└── chunks/
    ├── 00000000.part
    ├── 00000001.part
    └── 00000002.part

Configure chunk size

The default chunk size is 64 MiB.

var options = new FileTransportOptions
{
    ChunkSizeBytes = 128 * 1024 * 1024, // 128 MiB
    BufferSizeBytes = 1024 * 1024       // 1 MiB I/O buffer
};

var manifest = await splitter.SplitAsync(
    @"C:\Files\large-file.zip",
    @"C:\Packages\large-file",
    options);

ChunkSizeBytes must be at least 1 KiB.

BufferSizeBytes must be at least 4 KiB.

Compress chunks

Enable GZip compression with CompressChunks.

var options = new FileTransportOptions
{
    ChunkSizeBytes = 64 * 1024 * 1024,
    CompressChunks = true
};

await splitter.SplitAsync(
    @"C:\Files\large-file.zip",
    @"C:\Packages\large-file",
    options);

Compression is useful when the source data is compressible. Already-compressed files such as ZIP, JPEG, MP4, and many installers may see little or no reduction.

The manifest records whether compression was used, so assembly automatically decompresses the chunks.

Assemble a file

For a package containing one file, no original filename is required:

using EonaCat.FileSplitter;

var assembler = new FileAssembler();

await assembler.AssembleAsync(
    packageDirectory: @"C:\Packages\large-file",
    destinationFile: @"C:\Restored\large-file.zip");

The assembler:

  1. Reads manifest.json.
  2. Reads each chunk in order.
  3. Decompresses compressed chunks when required.
  4. Verifies every chunk's SHA-256 hash.
  5. Verifies the final file SHA-256 hash.
  6. Writes the result to a temporary file.
  7. Moves the completed file into its final location.

If validation fails, an InvalidDataException is thrown.

Assemble a specific file from a multi-file package

When a package contains multiple files, specify the original filename:

await assembler.AssembleAsync(
    packageDirectory: @"C:\Packages\my-package",
    destinationFile: @"C:\Restored\report.pdf",
    originalFileName: "report.pdf");

The filename is matched case-insensitively against the package index.

Split multiple files

Use SplitManyAsync to create one package containing multiple files:

var files = new[]
{
    @"C:\Files\report.pdf",
    @"C:\Files\data.csv",
    @"C:\Files\archive.zip"
};

List<FileManifest> manifests = await splitter.SplitManyAsync(
    files,
    @"C:\Packages\my-package");

The resulting package has an index.json at its root and one directory per file:

my-package/
├── index.json
├── file_0000/
│   ├── manifest.json
│   └── chunks/
│       ├── 00000000.part
│       └── ...
├── file_0001/
│   ├── manifest.json
│   └── chunks/
│       └── ...
└── file_0002/
    ├── manifest.json
    └── chunks/
        └── ...

List files in a package

Before assembling a multi-file package, you can inspect its contents:

var files = await assembler.ListPackageFilesAsync(
    @"C:\Packages\my-package");

foreach (var file in files)
{
    Console.WriteLine($"{file.OriginalFileName} - {file.FileLength:N0} bytes");
    Console.WriteLine($"SHA-256: {file.OverallSha256}");
}

Each PackageFileInfo provides:

  • OriginalFileName
  • RelativePath
  • FileLength
  • OverallSha256

Assemble all files

To restore every file in a package:

List<string> restoredFiles = await assembler.AssembleAllAsync(
    packageDirectory: @"C:\Packages\my-package",
    destinationDirectory: @"C:\Restored");

foreach (string file in restoredFiles)
{
    Console.WriteLine($"Restored: {file}");
}

Existing files are not silently overwritten when multiple package entries have the same filename. A numeric suffix is used instead:

report.pdf
report (2).pdf
report (3).pdf

Progress reporting

Single-file progress

Use IProgress<TransferProgress>:

var progress = new Progress<TransferProgress>(p =>
{
    Console.WriteLine(
        $"{p.Percent:F1}% - " +
        $"{p.ProcessedBytes:N0}/{p.TotalBytes:N0} bytes");
});

await splitter.SplitAsync(
    @"C:\Files\large-file.bin",
    @"C:\Packages\large-file",
    progress: progress);

TransferProgress contains:

Property Description
ProcessedBytes Number of source/destination bytes processed
TotalBytes Total file size
CompletedChunks Number of chunks processed
TotalChunks Total number of chunks
Percent Completion percentage

Multi-file progress

For operations involving multiple files, use MultiFileTransferProgress:

var progress = new Progress<MultiFileTransferProgress>(p =>
{
    Console.WriteLine(
        $"File {p.FileIndex + 1}/{p.FileCount}: " +
        $"{p.CurrentFileName} - " +
        $"{p.FileProgress.Percent:F1}%");
});

await splitter.SplitManyAsync(
    files,
    @"C:\Packages\my-package",
    progress: progress);

The same type can be used with AssembleAllAsync:

await assembler.AssembleAllAsync(
    @"C:\Packages\my-package",
    @"C:\Restored",
    progress: progress);

Cancellation

All asynchronous operations support cancellation:

using var cts = new CancellationTokenSource();

var task = splitter.SplitAsync(
    @"C:\Files\large-file.bin",
    @"C:\Packages\large-file",
    cancellationToken: cts.Token);

// Cancel from another part of your application:
// cts.Cancel();

await task;

You can also cancel assembly:

await assembler.AssembleAsync(
    @"C:\Packages\large-file",
    @"C:\Restored\large-file.bin",
    cancellationToken: cts.Token);

An OperationCanceledException is expected when cancellation occurs.

WPF example

IProgress<T> works naturally with WPF. Progress<T> posts callbacks back to the synchronization context:

private async Task SplitFileAsync()
{
    var progress = new Progress<TransferProgress>(p =>
    {
        ProgressBar.Value = p.Percent;
        StatusText.Text =
            $"{p.Percent:F1}% ({p.ProcessedBytes:N0} / {p.TotalBytes:N0})";
    });

    var options = new FileTransportOptions
    {
        ChunkSizeBytes = 64 * 1024 * 1024,
        BufferSizeBytes = 1024 * 1024,
        CompressChunks = false
    };

    var splitter = new FileSplitter();

    await splitter.SplitAsync(
        @"C:\Files\large-file.bin",
        @"C:\Packages\large-file",
        options,
        progress);
}

WinForms example

The same API can be used from WinForms:

private async Task AssembleFileAsync()
{
    var progress = new Progress<TransferProgress>(p =>
    {
        progressBar.Value = Math.Min(100, (int)p.Percent);
        statusLabel.Text = $"{p.Percent:F1}%";
    });

    var assembler = new FileAssembler();

    await assembler.AssembleAsync(
        @"C:\Packages\large-file",
        @"C:\Restored\large-file.bin",
        progress: progress);
}

For general-purpose file transport:

var options = new FileTransportOptions
{
    ChunkSizeBytes = 64 * 1024 * 1024,
    BufferSizeBytes = 1024 * 1024,
    CompressChunks = false,
    HashDegreeOfParallelism = 2
};

For compressible data:

var options = new FileTransportOptions
{
    ChunkSizeBytes = 64 * 1024 * 1024,
    BufferSizeBytes = 1024 * 1024,
    CompressChunks = true
};

HashDegreeOfParallelism is part of the transport options model and defaults to 2.

Package format

A package contains JSON metadata plus chunk files.

manifest.json

The manifest stores information such as:

  • Original filename
  • Original file size
  • Chunk size
  • Compression state
  • Overall SHA-256 hash
  • Individual chunk metadata

Each chunk entry contains:

  • Chunk index
  • Original file offset
  • Original chunk length
  • SHA-256 hash
  • Stored chunk length

index.json

Multi-file packages contain an index.json that maps each original filename to its package entry.

Example structure:

{
  "Version": 1,
  "Files": [
    {
      "OriginalFileName": "report.pdf",
      "RelativePath": "file_0000",
      "FileLength": 12345678,
      "OverallSha256": "..."
    }
  ]
}

Integrity and safety

Every chunk is protected by a SHA-256 hash. During assembly, the library validates:

  1. The chunk can be read.
  2. The decompressed chunk has the expected length.
  3. The chunk SHA-256 matches the manifest.
  4. The final assembled file SHA-256 matches the manifest.

The assembled output is first written to a temporary file and only moved into the requested destination after successful validation.

The assembler also validates chunk paths from the manifest to prevent unsafe paths from escaping the package's chunks directory.

Error handling

Typical exceptions include:

  • FileNotFoundException — source, manifest, index, or chunk is missing.
  • ArgumentException — invalid or missing input.
  • ArgumentOutOfRangeException — invalid transport settings.
  • InvalidOperationException — attempting to assemble a multi-file package without selecting a file.
  • InvalidDataException — corrupted chunk, invalid manifest data, or final SHA-256 mismatch.
  • OperationCanceledException — operation was cancelled.

Example:

try
{
    await assembler.AssembleAsync(
        @"C:\Packages\large-file",
        @"C:\Restored\large-file.bin");
}
catch (InvalidDataException ex)
{
    Console.WriteLine($"Package integrity check failed: {ex.Message}");
}
catch (OperationCanceledException)
{
    Console.WriteLine("Operation cancelled.");
}

API overview

FileSplitter

Method Purpose
SplitAsync(...) Split one file into a package
SplitManyAsync(...) Split multiple files into one package

FileAssembler

Method Purpose
ListPackageFilesAsync(...) List files contained in a package
AssembleAsync(...) Assemble one selected file
AssembleAllAsync(...) Assemble every file in a package

Models

  • FileTransportOptions
  • FileManifest
  • ChunkManifest
  • PackageIndex
  • PackageEntry
  • TransferProgress
  • MultiFileTransferProgress
  • PackageFileInfo

Example workflow

A typical send/receive workflow looks like this:

// Sender
var splitter = new FileSplitter();

await splitter.SplitAsync(
    @"C:\Files\video.mp4",
    @"C:\Transfer\video-package",
    new FileTransportOptions
    {
        ChunkSizeBytes = 128 * 1024 * 1024,
        CompressChunks = false
    });

// Transfer the package directory using your preferred transport.

// Receiver
var assembler = new FileAssembler();

await assembler.AssembleAsync(
    @"C:\Received\video-package",
    @"C:\Files\video.mp4");

The receiver does not need to manually calculate hashes or know whether compression was enabled.

Requirements

  • .NET Standard 2.0 compatible runtime/application.
  • File-system access to the source and destination locations.
  • Sufficient disk space for the package and assembled output.

License

See LICENSE.

Repository

https://git.saey.me/EonaCat/EonaCat.FileSplitter

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 103 9/2/2026