Commit graph

6 commits

Author SHA1 Message Date
BoulderBadgeDad
4d2772765c Add Aria2 to the torrent client list (Shdjfgatdif's request)
New Aria2 JSON-RPC adapter, alongside qBittorrent / Transmission / Deluge. Aria2's
RPC (default :6800/jsonrpc) maps cleanly onto the uniform adapter contract:
- the --rpc-secret token leads every call as "token:<secret>" (no username — the
  secret uses the existing password field),
- addUri returns a GID (our torrent id); tellStatus → TorrentStatus with state
  mapping (active→downloading, or seeding once the payload is complete; waiting→
  queued; etc.),
- remove picks forceRemove vs removeDownloadResult by status, and (since aria2
  doesn't delete files on remove) unlinks the file paths itself for delete_files,
- bare-host URLs get /jsonrpc appended.

Wired into adapter_for_type + the Settings dropdown (with a help note: port 6800,
secret in the Password field). All adapter methods go through the same interface,
so the stall/orphan handling and downloads pipeline work unchanged.

Tests (9): registry wiring, state mapping (incl. active→seeding), token-prefixed
params, /jsonrpc fixup, status parse (+ name fallback, no div-by-zero). 126 torrent
tests green; ruff clean.
2026-06-11 22:29:40 -07:00
BoulderBadgeDad
b5d22bede5 Fix #790: torrent client URL without http:// scheme fails to connect
A bare host like '192.168.1.5:8080' or 'qbittorrent.lan:8080' (no scheme)
is what users naturally type, but requests then raises 'No connection
adapters were found for ...' — it can't pick an http/https adapter, and a
bare host:port even gets misparsed as scheme=host. This surfaced as the
generic 'qbittorrent probe failed' with a 'login error: No connection
adapters were found' in the logs.

Add normalize_client_url() in torrent_clients/base: default a missing scheme
to http:// (+ trim trailing slash), and route all three adapters'
_load_config through it. Transmission normalizes the base before appending
/transmission/rpc.

Tests: normalizer unit cases + per-adapter regression (bare host -> http://).

Note: usenet adapters (sabnzbd/nzbget) share the same pattern and need the
same treatment in a follow-up.
2026-06-04 11:57:53 -07:00
BoulderBadgeDad
45f91fd318 Fix: qBittorrent 5.2.0+ login probe fails (HTTP 204 not handled)
qBittorrent 5.2.0 changed /api/v2/auth/login to return HTTP 204 (No Content)
on success instead of HTTP 200 with body 'Ok.'. The adapter required the body
to equal 'Ok.', so every login on 5.2.0+ failed with 'HTTP 204 body=' — the
connection probe and all torrent actions were broken.

Treat login as successful on the SID auth cookie and/or a success body: 'Ok.'
(<=5.1) or an empty HTTP 204 (>=5.2.0). Still reject bad creds, which
qBittorrent reports as HTTP 200 + 'Fails.' (not a 4xx).

Tests: 204-empty -> success, SID-cookie+empty-body -> success, 'Fails.' (even
with a stale cookie) -> failure.
2026-06-03 21:43:11 -07:00
Broque Thomas
e83b661471 fix(torrent): use before/after diff to recover qBit info-hash
Live testing surfaced: every download attempt failed with
'Torrent client refused the URL', but qBit was actually accepting
the add request fine. The bug was in our hash-lookup strategy.

qBittorrent's /api/v2/torrents/add returns 200 'Ok.' regardless
of whether the URL was actually valid / accepted / registered.
The previous code then queried /torrents/info?category=soulsync
to find the just-added torrent — but qBit hadn't categorised
the new torrent yet on the first poll, AND a fresh install has
no 'soulsync' category configured, so the lookup returned empty
and the adapter reported failure for every working download.

New strategy:
- Snapshot every torrent hash qBit currently tracks BEFORE
  posting to /add.
- POST /add, accept its (uninformative) 200 OK.
- Poll the all-torrents list for up to 5 seconds, looking for a
  hash that wasn't present in the before-snapshot. First new
  hash wins.

The diff strategy works the same for /add with urls= (HTTP URL /
magnet) and /add with files= (raw .torrent upload), so both
paths now share the same _all_hashes + _poll_for_new_hash
helpers. Adds a warning log when qBit returns an unexpected
body and an error log when no new hash appears (so future
investigation has breadcrumbs).
2026-05-20 18:11:30 -07:00
Broque Thomas
b475dc5a20 fix(lint): silence ruff B007 + S110
- core/torrent_clients/transmission.py: rename unused loop var
  `attempt` to `_attempt` in the session-id renegotiation loop
  (B007 — loop var not used in body).
- core/image_cache.py: log the cleanup exception instead of
  swallowing it silently (S110 — bare try/except/pass). debug
  level since a failed tmp unlink is non-fatal; the outer
  ``raise`` still propagates the original error.

Full ruff sweep clean.
2026-05-20 16:18:55 -07:00
Broque Thomas
de2faf290b feat(torrent): add adapter layer for qBittorrent, Transmission, Deluge
Second commit in the torrent + usenet rollout. SoulSync now speaks
three different BitTorrent client APIs through one uniform adapter
contract — picks the active client by config and dispatches the same
verbs to whichever backend the user uses. Each adapter handles its
own auth quirk (qBit cookie + CSRF Referer, Transmission session-id
renegotiation, Deluge JSON-RPC session) and maps native state
strings onto a shared 7-value set so the rest of the app stays
client-agnostic.

- core/torrent_clients/base.py: TorrentClientAdapter Protocol +
  TorrentStatus dataclass. Eight verbs: is_configured, check_connection,
  add_torrent (URL/magnet), add_torrent_file (raw bytes), get_status,
  get_all, remove, pause, resume.
- core/torrent_clients/__init__.py: adapter_for_type factory +
  get_active_adapter that reads torrent_client.type each call so
  settings changes take effect without restart.
- core/torrent_clients/qbittorrent.py: WebUI v2 adapter. Cookie auth
  via /api/v2/auth/login, transparent 403 re-login, Referer header
  to satisfy qBit's CSRF guard. add_torrent returns the just-added
  hash via /torrents/info sort=added_on (qBit's add endpoint doesn't
  echo the hash).
- core/torrent_clients/transmission.py: RPC adapter. Auto-resolves
  bare host URLs to /transmission/rpc, handles the 409 + new
  X-Transmission-Session-Id renegotiation transparently, accepts
  HTTP basic auth. add_torrent_file base64-encodes payload per spec.
- core/torrent_clients/deluge.py: Deluge 2.x JSON-RPC adapter.
  Password-only auth, distinguishes magnet vs HTTP URL at the RPC
  method layer, applies category via Label plugin (best-effort —
  label plugin is optional).
- core/connection_test.py: 'torrent_client' branch picks the right
  adapter, runs check_connection, surfaces a per-client error
  message.
- config/settings.py: torrent_client.{type, url, username, password,
  category, save_path} defaults + torrent_client.password in the
  encrypted-at-rest secrets list.
- web_server.py: 'torrent_client' added to the /api/settings POST
  allow-list so saved config persists.
- webui/index.html: new Torrent Client panel on the Indexers &
  Downloaders tab — client-type dropdown, URL, username, password,
  category, optional save path, Test Connection.
- webui/static/settings.js: load/save wiring + testTorrentClientConnection.
- webui/static/helper.js: WHATS_NEW + VERSION_MODAL_SECTIONS entry.
2026-05-20 15:10:30 -07:00