- Revert GetFileName to return unsanitized name. AllDebridDebridClient.
GetSymlinkPath matches torrent.Files[].Path (original debrid data)
against GetFileName, so sanitizing it broke AllDebrid Symlink lookups
for any torrent with bracketed filenames. Sanitization now happens
inside GetDownloadPath at the point the filesystem path is assembled,
while matching still uses the unsanitized name.
- Make SanitizeFilename / SanitizePath accept String? and return
String.Empty for null/empty input so the declared non-null return
type is honoured.
- SanitizePath splits on both '/' and '\\' regardless of host OS, so
paths arriving from the debrid provider with foreign separators are
still segmented correctly on Linux containers.
- Rewrite the control-character tests to use explicit \u escape
sequences (, , , , ) so the test
inputs are visible in diffs and survive editor/CI round-trips.
Replace the single-row empty [Theory] with a [Fact] and add a null
case. Add tests asserting SanitizeFilenameIfEnabled and
SanitizePathIfEnabled return input unchanged when the toggle is
disabled, and sanitize when enabled.
Downloads fail on Linux containers when filenames contain square
brackets or multiple consecutive spaces. On Linux, .NET's
Path.GetInvalidFileNameChars() only returns NUL and '/', so characters
like [ ] { } that cause issues with shell globbing, URI handling, and
various download clients pass through the existing filter unchanged.
Add FilenameSanitizer that:
- Strips square brackets, curly braces, and control characters
- Collapses multiple consecutive spaces into one
- Trims leading and trailing whitespace
- Preserves parentheses and all other characters
Apply sanitization at filesystem boundaries: DownloadHelper (where the
Real-Debrid filename is turned into a local path), UnpackClient,
Torrent delete / RunOnTorrentComplete, and the qBittorrent / SABnzbd
status paths that *arr reads back. Aria2c and DownloadStation also
sanitize their remote output paths so they match the sanitized local
filePath. Symlink's rclone-mount lookup still uses the original name
since that has to match the real file in the mount.
Add a SanitizeFilenames toggle in DbSettings (default on) so users
can disable the behaviour if they need the exact Real-Debrid name.
The previous fix compared subPath directly against the sanitized torrent
name, which fails when RdName contains characters removed by
RemoveInvalidPathChars (e.g. ':' on Windows). Extract and normalize the
first component of subPath before comparing so both sides go through the
same sanitization. Add tests covering the duplicate-prefix stripping case.
- Applied to TorBox, can be extended easily to other providers
- Reads response headers commonplace for pre-emptive rate limit throttling and retry-after
- When a rate limit is reached, displays a warning in the UI
- Fix socket leak from direct allocation of HttpClient, replaced with factory which handles pooling and re-use of sockets.
While HttpClient is Disposable, it doesn't gaurantee (and does not) directly release underlying sockets for queries at the time the client is disposed. These sockets will go into a TCP WAIT state often for a very long time. The expected pattern in C# is to always use the HttClientFactory which will correctly handle re-use of the OS sockets in suqsequent queries reducing resource and memory leaks.
- Fixes an issue where progress goes back to 50% as soon as downloads are done
- Prevents copy and update of torrent/nzb list by not updating download stats on every use, most callers don't require this data, notable perf/memory improvement.
Strengthened URL Validation.
Improved HTTP Client Configuration.
Logged total rejected tracker count at the information level.
Logged individual rejected tracker details at the debug level.
Debug logs for bytes and enriched bytes are now conditional.
Converted some tests from Fact to Theory.
Used Assert.Collection in EnricherTest.cs.
Standardized URL Encoding in Enricher.cs.
`DownloadableFileFilter` is an abstraction of the filtering we do in most of the `TorrentClient`s.
It operates on a single file at a time, not the entire torrent.
Tests:
- `GetDownloadLinks`
- `IncludeRegex` filter works
- `ExcludeRegex` filter works
- `DownloadMinSize` filter works
- only `IncludeRegex` is used when both `IncludeRegex` and `ExcludeRegex` are set
- gets links for all files if none of the above filters are set
- returns null if `torrent.RdId` is null
- `GetTorrents`
- [Both full/partial sync] returns empty list when no torrents present in api response
- [full sync] overrides any previous torrents in cache, deleting those not present in the api response
- [partial sync] updates torrents in cache according to the api response
- [partial sync] adds torrents to cache according to the api response
- `Map` (private so tested via `GetTorrents`)
- Computes progress correctly from `Downloaded / Size`
- `UpdateData`
- [`torrentClientTorrent != null`] does not get from api, updates the `torrent` accordingly
- [`torrentClientTorrent == null`] gets from api, updates the `torrent` accordingly
- [`torrentClientTorrent == null`] when torrent deleted from rd, updates `RdStatusRaw = "deleted""`
- when `torrent.RdId == null`, returns the `torrent` unmodified
Tests:
- [Both overloads] uses the `download.Link` to guess the filenamem if it's not present
- [Both overloads] returns null if `download.Link` is null
- [Both overloads] returns nuyll if `download.RdName` is null
- [Both overloads] works for a file in a deeply nested subdirectory
- [overload with `downloadPath` argument only] creates the correct directory
Tests:
- Downloads successfully when everything goes right first time
- Downloads successfully when the 5th api call is successful after 4 failures
- Fails to download when 5 api calls fail in a row
- Fails to download if the file has already been added to `Download Station` (when `GetInfoAsync` does not error)
- FAils to download if `remotePath` cannot be parsed by `Path.GetDirectoryName()`