Skip to content

Releases: bezzad/Downloader

v5.9.5

Choose a tag to compare

@github-actions github-actions released this 20 Jul 06:45

Highlights

Fixed

  • Issue #239 — rare IOException: The process cannot access the file … .download when finalizing a download. The library deleted its temp/target files with a bare File.Delete, which threw immediately if an external process (typically an antivirus real-time scan of the freshly written file) still held a handle. All downloader-owned file deletions now retry with exponential backoff (6 attempts over ~3 seconds), which rides out transient scans of even large files. If the lock never clears, the failure is no longer a bare IOException: the error now explains that an external process (antivirus or another program) is holding the file and how to resolve it, with the original exception preserved as InnerException. (#239)
  • RemoteFileResolver.GetFileInfoAsync no longer throws on slow/unreachable hosts. The internal connect-timeout cancellation (TaskCanceledException) was being rethrown as a hard failure even though the caller's cancellation token was never signaled, breaking the documented fallback to the URL-derived file name. The rethrow is now gated on the caller's token; unreachable hosts fall back as intended.

Added

  • .NET 11 (preview) supportnet11.0 target added to the library and validated by CI on Ubuntu, macOS, and Windows alongside net8.0/net9.0/net10.0.

Internal

  • Test coverage raised from 88.7% to 90.2% with new deterministic unit tests; several flaky tests (stop/resume timing, Windows CI file-lock cleanup) made deterministic; test project switched to portable PDBs to fix a Fody crash on the Windows net11.0 CI leg.

Full Changelog: v5.9.4...v5.9.5

v5.9.4

Choose a tag to compare

@github-actions github-actions released this 10 Jul 11:17

Fixed — resume after a failed download attempt

  • Resume no longer restarts from 0% after a transient failure. A timeout, dropped connection, or 5xx (503/504) during a resume no longer discards already-downloaded chunk progress when the single-connection fallback engages. A failed attempt now stays resumable from its last position.
  • Fixed silent file corruption when resuming a multi-chunk download against a server that reports no range support. The stale chunk layout is now rebuilt so restarted bytes are written at the correct offsets.
  • Fixed an off-by-one in the chunk-completion check that treated a chunk left exactly one byte short as complete, which could fail a download near 100% and make its final byte impossible to resume. (Paired with a range-request boundary fix so the final byte of a resumed chunk is no longer corrupted.)

Full Changelog: v5.9.1...v5.9.4

v5.9.1

Choose a tag to compare

@github-actions github-actions released this 09 Jul 21:35

Highlights

  • Fixed #236: downloaded file truncated when a caller's custom HttpClient auto-decompresses gzip/deflate content. The server advertises a Content-Length equal to the compressed byte count, so chunking against that number and writing the decompressed bytes overran and cut the file short. The size probe (SocketClient) now treats a non-identity Content-Encoding on the response as unknown size + no range support, routing such downloads through the existing single-connection / unknown-size path instead of splitting the compressed length into parallel range chunks.
  • Guidance: a transparently-compressed URL is downloaded on a single connection because a gzip stream cannot be range-split by output-byte offset. To keep multi-part parallel download for such a URL, set AutomaticDecompression = None on your custom HttpClient so the bytes on the wire match the advertised Content-Length and the file stays range-addressable.

Full Changelog: v5.9.0...v5.9.1

v5.9.0 — Resolve file name & size without downloading

Choose a tag to compare

@bezzad bezzad released this 19 Jun 10:28

Downloader v5.9.0

✨ Resolve a remote file's name and size — without downloading it

This release exposes the engine's internal "what is this remote file?" logic as a clean public API, so you can preview a file's name and size (and whether it supports ranged/resumable downloads) from a single lightweight header probe — no need to start and immediately stop a real download just to learn its name. Perfect for populating a queue/grid of pending downloads.

// Just the name (resilient — falls back to the URL name on a network/server error):
string name = await RemoteFileResolver.GetFileNameAsync(url);

// Full metadata in one probe:
RemoteFileInfo info = await RemoteFileResolver.GetFileInfoAsync(url);
// info.FileName, info.FileSize (-1 if unknown), info.SupportsRange, info.Address (final, post-redirect)

Both methods accept an optional DownloadConfiguration (headers, proxy, credentials, cookies, redirect policy) and a CancellationToken.

🆕 What's new

  • RemoteFileResolver (new public static helper) — GetFileNameAsync(url) and GetFileInfoAsync(url) returning the new RemoteFileInfo record { FileName, FileSize, SupportsRange, Address }. Name resolution: Content-Disposition → URL path → GUID. Size: Content-RangeContent-Length.
  • IDownloadService.GetFileInfoAsync(url) — the same lookup on the download service, using that service's configuration, without disturbing any download in progress.

🔧 Internal

  • Filename/size/range resolution is now unified behind the canonical SocketClient.GetFileInfoAsync, consumed by both the download pipeline (DownloadService) and RemoteFileResolver — removing duplicated probing logic.
  • No behavior change to existing downloads (verified against the full test suite: 165 regression tests + new resolver tests, green on .NET 8/9/10).

📦 Install

dotnet add package Downloader --version 5.9.0

Full Changelog: v5.8.1...v5.9.0

version 5.8.1

Choose a tag to compare

@bezzad bezzad released this 16 Jun 11:50
  • Fixed downloads failing on CDN cookie-challenge links (e.g. ArvanCloud/Cloudflare "307 to self" walls): the downloader now follows same-URL redirects — bounded by MaximumAutomaticRedirections — instead of refusing a redirect whose Location equals the current address, and clears the stale 3xx response headers before re-probing so the redirect target is actually fetched (previously the cached-headers guard short-circuited and the redirect was skipped).
  • RequestConfiguration.CookieContainer now defaults to a new CookieContainer, so session/challenge cookies issued mid-redirect are stored and replayed on the follow-up request. Set it to null to disable cookie handling entirely. (Note: this is a minor behavior change — cookies are now enabled by default within a download session.)
  • Limitations: this does not defeat JavaScript-based bot challenges (the cookie value must be computed by a browser) nor revive expired signed links — such URLs return a small HTML page with HTTP 200, so inspect the response before assuming a download bug.

version 5.8.0

Choose a tag to compare

@bezzad bezzad released this 02 Jun 13:35
  • Fixed issue #226 (AOT downloads failing with HTTP 428): the real cause was that some CDNs (e.g. BunnyCDN) return 428 as a per-client concurrency throttle for parallel chunks, and error classification relied on Exception.Source, which is empty under AOT/trimming — making the 428 fatal only in AOT. Error retry-ability is now decided by exception type and HTTP status code, deterministically across JIT and AOT.

  • Retries now use exponential backoff with full jitter instead of a fixed delay, so chunks throttled together (428/429/503) disperse on retry instead of repeating the burst.

  • 428 Precondition Required and transport-level HttpRequestExceptions (no HTTP status) are now treated as transient/retryable; permanent client errors (400/401/403/404) and 500/502 remain non-retryable.

  • Fixed issue #231 (follow-up): in environments where a TLS-inspecting proxy/antivirus breaks concurrent HTTPS connections, parallel/range chunk downloads failed (SEC_E_DECRYPT_FAILURE, "response ended prematurely", aborted sockets) even though a single sequential connection worked. The download service now automatically falls back to a single connection and completes the download instead of failing, when a multi-connection attempt fails with a transient transport error.

version 5.6.0

Choose a tag to compare

@bezzad bezzad released this 01 Jun 05:55

Full Changelog: v5.4.0...v5.6.0

  • Fixed issue #225: A download cancelled under poor network conditions now ends as Stopped instead of Failed, even when a network/SSL error is thrown during cancellation.
  • Fixed issue #230: DownloadProgressChanged no longer reports 100% immediately when the server omits Content-Length; progress stays at 0% (unknown size) until the download completes.
  • Fixed issue #231: A response stream that ends prematurely (before the advertised size) is no longer silently treated as a completed download; it now retries/resumes and, when it cannot, fails with an IncompleteDownloadException instead of leaving an unfinished .download file.

version 5.4.0

Choose a tag to compare

@bezzad bezzad released this 29 Apr 11:24
  • Fixed issue #228: IsAotCompatible and PublishAot properties added to enable AOT compilation for .NET 8 and later.

Full Changelog: v5.3.0...v5.4.0

version 5.3.0

Choose a tag to compare

@bezzad bezzad released this 25 Apr 12:05

What's Changed

  • Fix #223: URI encode links by @XanderLuciano in #224
    • The issue was caused by incorrect URL encoding of square brackets in the download URL.
    • This has been resolved to ensure proper handling of such URLs.

New Contributors

Full Changelog: v5.2.0...v5.3.0

version 5.2.0

Choose a tag to compare

@bezzad bezzad released this 21 Apr 08:49

Fixed issue #220: Some servers don't like the Range header and respond with errors like 403, 404 or 503. even though the file is perfectly downloadable with a normal request (no Range header).

Full Changelog: v5.1.1...v5.2.0