Downloader .NET Library

repository·master·Indexed 23 days ago

https://github.com/bezzad/downloader

A fast, cross-platform multipart downloader library for .NET featuring parallel chunking, pause/resume functionality, and real-time progress tracking. It supports asynchronous file downloads to disk or memory streams, dynamic speed limits, and automatic resume capabilities. The library provides a fluent DownloadBuilder API and a DownloadService for fine-grained control over HTTP request headers, retries, and custom HttpClient integration. Compatible with .NET 8, 9, and 10 (v5.x) as well as .NET Standard 2.1 and .NET Framework 4.6.1 (v3.1.*).

Tokens
7.1K
Snippets
15
Records
22
Agent score
83%

What's inside Downloader

  1. Key features of Downloader

    master

    Downloader is a high-performance, cross-platform multipart downloader for .NET with the following capabilities:

    • Multipart Downloads: Downloads files in multiple parallel parts for increased speed.
    • Resilience: Supports automatic and manual resume, and is resilient to client/server errors.
    • Efficiency: Pre-allocates file size before downloading and saves chunks directly into the final file without temporary files (though it uses a .download extension during the process).
    • Control: Supports dynamic speed limits, configurable ChunkCount, and downloading specific byte ranges.
    • Flexibility: Supports downloading to disk or directly to memory streams, and allows injecting custom HttpClient or HttpMessageHandler.
    • Metadata: Provides real-time speed, progress updates, and remote file resolution (name/size) without downloading the file via RemoteFileResolver.
  2. Handle transient file locks and external process interference

    master

    The library is designed to be resilient against transient IOException: file in use errors, which commonly occur on Windows when external processes (like antivirus real-time scanners) momentarily lock a file being written or deleted.

    If a permanent lock is held by an external process that does not release the handle, the library will throw an IOException with a descriptive message explaining that the lock is held by an external process (e.g., AV/other program) and suggesting how to resolve it. The original exception is preserved as the InnerException.

  3. Enable automatic resume downloads

    master

    The recommended way to handle interruptions (crashes, network drops) is to enable EnableAutoResumeDownload = true in your DownloadConfiguration.

    How it works:

    1. Temporary Files: The downloader always uses a temporary file with the extension specified by DownloadFileExtension (default is .download).
    2. Metadata Embedding: When EnableAutoResumeDownload is true, the downloader appends a JSON-serialized DownloadPackage (containing chunk positions and file size) to the end of the .download file.
    3. Automatic Recovery: On a subsequent call to DownloadFileTaskAsync for the same file, the downloader detects the .download file, reads the metadata from the end, verifies the server still supports ranges, and resumes from the last known chunk positions.
    4. Cleanup: Upon successful completion, the downloader truncates the file to remove the metadata and renames it to the original filename.
    var downloadOpt = new DownloadConfiguration()
    {
        EnableAutoResumeDownload = true,
        DownloadFileExtension = ".download" 
    };
    
    var downloader = new DownloadService(downloadOpt);
  4. Configure a custom HttpClient or HttpMessageHandler

    master

    To integrate with IHttpClientFactory, add custom authentication, or use specific SSL settings, you can provide a custom HttpClient or HttpMessageHandler via DownloadConfiguration or the DownloadBuilder.

    Important Precedence Rule

    If both CustomHttpClientFactory and CustomHttpMessageHandlerFactory are set, CustomHttpClientFactory takes precedence and CustomHttpMessageHandlerFactory is ignored.

    Option 1: Custom HttpClient

    Use CustomHttpClientFactory for full control. The Downloader will skip all internal handler and header configuration.

    Option 2: Custom HttpMessageHandler

    Use CustomHttpMessageHandlerFactory to customize only the handler (e.g., for caching or custom SSL), while letting the Downloader still configure default request headers and timeouts.

    // Using DownloadConfiguration with CustomHttpClientFactory
    var downloadOpt = new DownloadConfiguration()
    {
        ChunkCount = 8,
        ParallelDownload = true,
        CustomHttpClientFactory = () => {
            return httpClientFactory.CreateClient("MyDownloader");
        }
    };
    
    // Using DownloadConfiguration with CustomHttpMessageHandlerFactory
    var downloadOpt = new DownloadConfiguration()
    {
        ChunkCount = 8,
        ParallelDownload = true,
        CustomHttpMessageHandlerFactory = () => {
            return new SocketsHttpHandler {
                MaxConnectionsPerServer = 500,
                PooledConnectionLifetime = TimeSpan.FromMinutes(10)
            };
        }
    };
    
    // Using the fluent builder API
    await DownloadBuilder.New()
        .WithUrl(url)
        .WithDirectory(@"C:\temp")
        .WithHttpClient(() => httpClientFactory.CreateClient("MyDownloader"))
        .Build()
        .StartAsync();
    
    await DownloadBuilder.New()
        .WithUrl(url)
        .WithDirectory(@"C:\temp")
        .WithHttpMessageHandler(() => new SocketsHttpHandler {
            MaxConnectionsPerServer = 500,
            PooledConnectionLifetime = TimeSpan.FromMinutes(10)
        })
        .Build()
        .StartAsync();
  5. Install the Downloader library

    master

    You can install the Downloader library using either the NuGet Package Manager or the .NET CLI.

    Using .NET CLI:

    dotnet add package Downloader

    Using NuGet Package Manager Console:

    PM> Install-Package Downloader

    Version Compatibility Note:

    • The v5.x line targets .NET 8, .NET 9, and .NET 10.
    • If you require compatibility with older runtimes like .NET Standard 2.1 or .NET Framework 4.6.1, you must use the v3.1.* line.
    dotnet add package Downloader
  6. Build a Native AOT version

    master

    You can build a standalone native executable using Ahead-of-Time (AOT) compilation for faster startup and lower memory usage. This requires the .NET 8.0 SDK or later.

    Build Commands

    Windows (x64):

    dotnet publish -r win-x64 -f net8.0 -c Release

    Linux (x64):

    dotnet publish -r linux-x64 -f net8.0 -c Release

    macOS (x64):

    dotnet publish -r osx-x64 -f net8.0 -c Release

    Output Location

    The compiled executable is located in: bin/Release/net8.0/<RUNTIME_IDENTIFIER>/publish/

  7. Serialize and deserialize DownloadPackage for manual resume

    master

    If you are not using EnableAutoResumeDownload = true (which handles metadata automatically in .download files), you must manually manage the DownloadPackage object to resume downloads after an application restart.

    JSON Serialization

    Use a JSON serializer to save the DownloadPackage state (URL, file path, chunk positions).

    Binary Serialization

    To save as a binary file, serialize the package to JSON first, then write it using a BinaryWriter. Do not use BinaryFormatter as it is deprecated and insecure.

    // JSON Serialization
    // Serialize
    var packageJson = JsonConvert.SerializeObject(package);
    
    // Deserialize
    var restoredPack = JsonConvert.DeserializeObject<DownloadPackage>(packageJson);
    
    // Resume
    await downloader.DownloadFileTaskAsync(restoredPack);
  8. Quick Start with DownloadBuilder

    master

    The fastest way to start a download is using the fluent DownloadBuilder API. This allows you to specify the URL, the target directory, and start the asynchronous download process immediately.

    await DownloadBuilder
        .New()
        .WithUrl(@"https://host.com/test-file.zip")
        .WithDirectory(@"C:\temp")
        .Build()
        .StartAsync();
  9. Pause and Resume downloads

    master

    Quick Pause/Resume

    To temporarily suspend a download while keeping the network streams alive, use the Pause() and Resume() methods on the DownloadService.

    downloader.Pause();
    downloader.Resume();

    Manual Stop and Resume (Persistent)

    If you need to stop a download and resume it later (even after an application restart), you must manually manage the DownloadPackage object.

    1. Capture the state: Access downloader.Package to get a snapshot of the current download state.
    2. Stop: Use downloader.CancelAsync() (fire-and-forget) or await downloader.CancelTaskAsync() (waits for stop).
    3. Resume: Pass the captured DownloadPackage back into DownloadFileTaskAsync(pack).

    Note: Resuming requires the server to support HTTP range requests.

    // Quick pause/resume
    downloader.Pause();
    downloader.Resume();
    
    // Manual persistent resume
    DownloadPackage pack = downloader.Package;
    
    // Stop
    await downloader.CancelTaskAsync();
    
    // Resume later (even after restart)
    await downloader.DownloadFileTaskAsync(pack);
  10. Configure cookies and redirects

    master

    The Downloader automatically follows HTTP redirects (up to MaximumAutomaticRedirections = 50). It also handles same-URL "challenge" redirects (e.g., a 307 redirect that adds a Set-Cookie).

    To ensure cookies are stored and replayed during redirects, use the CookieContainer in RequestConfiguration. You can set it to null to disable cookie handling entirely or provide your own container.

    var downloadOpt = new DownloadConfiguration
    {
        RequestConfiguration =
        {
            CookieContainer = myCookieContainer, // or null to disable cookies
        }
    };
  11. Configure DownloadConfiguration

    master

    The DownloadConfiguration object allows you to customize the behavior of the DownloadService. You can control chunking, parallelism, speed limits, retries, and HTTP request headers.

    Common Configuration Scenarios

    Simple Configuration

    Use this for basic parallel downloads.

    A good general-purpose starting point that uses parallel chunks, retries, and automatic resume capabilities.

    Complex Configuration

    Use this for fine-grained control over buffers, timeouts, range downloads, and custom RequestConfiguration (headers, proxy, authentication).

    // Recommended setup
    var downloadOpt = new DownloadConfiguration
    {
        ChunkCount = 8,         
        ParallelDownload = true,
        ParallelCount = 4,              
        MaxTryAgainOnFailure = 5,       
        EnableAutoResumeDownload = true,
        MaximumMemoryBufferBytes = 50 * 1024 * 1024, 
        CheckDiskSizeBeforeDownload = true, 
        MaximumBytesPerSecond = 0,   
    };
    
    // Complex configuration with RequestConfiguration
    var downloadOpt = new DownloadConfiguration()
    {
        // ... other properties ...
        RequestConfiguration = 
        {
            Accept = "*/*",
            CookieContainer = cookies,
            Headers = ["Accept-Encoding: gzip, deflate, br"],
            KeepAlive = true, 
            ProtocolVersion = HttpVersion.Version11,
            UserAgent = "Mozilla/5.0",
            Proxy = new WebProxy() {
               Address = new Uri("http://YourProxyServer/proxy.pac"),
               UseDefaultCredentials = false,
               Credentials = System.Net.CredentialCache.DefaultNetworkCredentials,
               BypassProxyOnLocal = true
            },
            Authorization = new AuthenticationHeaderValue("Bearer", "XX_YOUR_TOKEN_XX")
        }
    };
  12. Why multi-chunk downloads might fail

    master

    The Downloader attempts to split files into multiple chunks for faster downloading. However, it will fall back to a single chunk if any of the following conditions are met:

    • Missing Content-Length: The server does not provide the file size in the response header.
    • Accept-Ranges: none: The server explicitly states it does not support range requests.
    • Missing Content-Range: The server does not support range requests during the initial size check.
    • Compressed Responses: If RequestConfiguration.AutomaticDecompression is enabled and the server returns a compressed response (e.g., gzip, deflate, br), the Downloader falls back to a single chunk to prevent corruption, as byte offsets in a compressed stream do not map directly to the decompressed output.