Releases: bezzad/Downloader
Release list
v5.9.5
Highlights
Fixed
- Issue #239 — rare
IOException: The process cannot access the file … .downloadwhen finalizing a download. The library deleted its temp/target files with a bareFile.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 bareIOException: 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 asInnerException. (#239) RemoteFileResolver.GetFileInfoAsyncno 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) support —
net11.0target added to the library and validated by CI on Ubuntu, macOS, and Windows alongsidenet8.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.0CI leg.
Full Changelog: v5.9.4...v5.9.5
v5.9.4
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
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-Encodingon 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 = Noneon 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
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)andGetFileInfoAsync(url)returning the newRemoteFileInforecord{ FileName, FileSize, SupportsRange, Address }. Name resolution:Content-Disposition→ URL path → GUID. Size:Content-Range→Content-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) andRemoteFileResolver— 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
- 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
-
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
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
- 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
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
- @XanderLuciano made their first contribution in #224
Full Changelog: v5.2.0...v5.3.0
version 5.2.0
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