Init() created a new SynologyClient and logged in for every download. A
many-file download opened dozens of near-simultaneous sessions for one DSM
account; DSM invalidates older session IDs, so in-flight File Station calls
failed with error 119 "SID not found".
Add SynologyClientProvider: a thread-safe cache that authenticates once and
shares the client across downloads (concurrent first calls collapse to a
single login; later calls reuse it; a credential change re-authenticates).
Wrap every Synology call in a session-retry that drops the session and
re-authenticates once on a session error (119/106/107) so an expired SID
self-heals, reading credentials fresh from settings so edits take effect live.
Adds provider unit tests and a downloader test covering the 119 re-auth path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The DownloadStation downloader left downloads stuck or silently failing.
This fixes the core flow and documents setup:
- Create the per-download destination folder via File Station (force_parent)
before creating the task; DownloadStation does not create it for a
direct-file task, which otherwise fails with "Destination does not exist".
- Auto-set the account's default destination when it has none (DSM leaves
every task "Waiting" otherwise), preserving the other server settings.
- Detect completion across the terminal states (Finished/Downloaded/Seeding/
PreSeeding or fully transferred), then verify the file is visible at the
container path before signalling success, so a path-mapping mismatch is a
clear error instead of a silent import failure.
- Surface DownloadStation errors (status_extra.error_detail, captcha) instead
of polling progress forever.
- Harden task-list / get-info / delete handling against responses that fail
to deserialize (upstream #723/#792) so one bad task can't abort a download.
- Make task creation idempotent: adopt an existing task instead of throwing
"already added", which previously bricked every retry.
Adds unit tests, fixes a settings description typo, and documents Download
Station setup and the path-mapping requirement in the README.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removes GetHashInfoAsync, and only uses the actual torrentId.
This was originally required as cachedtorrents endpoints used to use a different torrentId to the regular torrent endpoints for whatever reason, but luckily they figured out a while ago that that was not a good idea and just combined them which means calling API to search for hashes isnt needed anymore, hopefully this will reduce rate limiting a bit.
- Refactored Torrents.cs to use local DbSettings instead of global Settings.Get
- Removed unused exception variable in Torrents.cs
- Disabled xUnit parallelization globally to prevent test race conditions caused by shared global state
- Enforced sequential test execution in CI workflow
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.