Files
ReadMeABook/documentation/phase3/qbittorrent.md
T
Claude a3381cba31 Fix qBittorrent service not reloading config after settings change
Root cause: Singleton caching issue
The qBittorrent service uses a singleton pattern with a configLoaded flag.
Once initialized, it NEVER re-reads the database config, even when the
user changes settings via the admin settings page.

Flow showing the bug:
1. Wizard saves download_dir to database ✓
2. First torrent: service reads config, creates singleton, sets configLoaded=true ✓
3. User changes download_dir in settings page ✓ (database updated)
4. Next torrent: getQBittorrentService() returns CACHED singleton ✗
5. Cached singleton has OLD download_dir value in this.defaultSavePath ✗
6. Category check shows "already has correct save path: /old/path" ✗
7. Download goes to wrong location ✗

The singleton check (line 745):
  if (!qbittorrentService || !configLoaded) {
    // Only runs if service doesn't exist or config failed
  }
Once both exist, this block is SKIPPED forever!

Fix:
1. Added invalidateQBittorrentService() function
   - Resets qbittorrentService = null
   - Resets configLoaded = false
   - Forces reload from database on next use

2. Call invalidation from settings APIs:
   - After updating paths (download_dir, media_dir)
   - After updating download client (URL, credentials)

3. Next torrent addition:
   - getQBittorrentService() sees null singleton
   - Re-reads config from database
   - Creates new service with current download_dir
   - Category updated with correct path

Benefits:
- Settings changes take effect immediately
- No app restart needed
- Category save path always matches current config
- Download client credentials always current

Updated documentation to explain singleton invalidation pattern.
2026-01-28 11:41:57 -05:00

4.6 KiB

qBittorrent Integration

Status: Implemented

Free, open-source BitTorrent client with comprehensive Web API.

Enterprise Torrent Addition

Challenge: /api/v2/torrents/add returns only "Ok." without torrent hash.

Solution (Professional):

Magnet Links:

  1. Extract info_hash from magnet URI (deterministic)
  2. Upload via urls parameter
  3. Return extracted hash immediately

Torrent Files:

  1. Download .torrent file to memory
  2. Parse with parse-torrent (bencode decoder)
  3. Extract info_hash (SHA-1 of info dict)
  4. Upload file content via torrents parameter (multipart/form-data)
  5. Return extracted hash immediately

Benefits: Deterministic, no race conditions, works with Docker networking, handles expired URLs

API Endpoints

Base: http://qbittorrent:8080/api/v2 Auth: Cookie-based (login required)

POST /auth/login - Get session cookie POST /torrents/add - Add torrent (supports urls and torrents params, savepath override) GET /torrents/info?hashes={hash} - Get status/progress POST /torrents/pause - Pause torrent POST /torrents/resume - Resume POST /torrents/delete - Delete torrent GET /torrents/files - Get file list POST /torrents/createCategory - Create category with save path POST /torrents/editCategory - Update category save path POST /torrents/setCategory - Set category for torrent

Config

Required (database only, no env fallbacks):

  • download_client_url - qBittorrent Web UI URL
  • download_client_username - qBittorrent username
  • download_client_password - qBittorrent password
  • download_dir - Download save path (passed to qBittorrent for all torrents)

Validation: All fields checked before service initialization.

Singleton Invalidation: Service uses singleton pattern for performance. When settings change (via admin settings page), singleton is invalidated to force reload:

  • invalidateQBittorrentService() called after updating paths or download client settings
  • Forces service to re-read database config on next torrent addition
  • Ensures category save path and credentials are always current

Category Management

Category: readmeabook (auto-created for all torrents)

Save Path Synchronization:

  • Category created/updated on every torrent addition
  • Category save path always synced with download_dir config
  • Handles config changes: if user changes download_dir, category updates automatically
  • Uses both createCategory and editCategory APIs for reliability

Why Both Create and Edit:

  1. Create: Ensures category exists (idempotent, won't fail if exists)
  2. Edit: Updates save path to match current config (handles user changing settings)

This prevents issues where category retains old save path after user changes download_dir setting.

Data Models

interface TorrentInfo {
  hash: string;
  name: string;
  size: number;
  progress: number; // 0.0-1.0
  dlspeed: number; // bytes/s
  upspeed: number;
  eta: number; // seconds
  state: TorrentState;
  category: string;
  savePath: string;
  completionDate: number;
}

type TorrentState = 'downloading' | 'uploading' | 'stalledDL' |
  'pausedDL' | 'queuedDL' | 'checkingDL' | 'error' | 'missingFiles';

Fixed Issues

1. Naive torrent identification - Fixed with deterministic hash extraction 2. Docker networking issues - Fixed by downloading .torrent ourselves 3. Duplicate detection - Check if hash exists before adding 4. Config fallbacks to env - Removed, database only 5. Unclear error messages - List missing fields explicitly 6. Race condition on torrent availability - Fixed with 3s initial delay + exponential backoff retry (500ms, 1s, 2s) 7. Error logging during duplicate check - Removed console.error in getTorrent() during expected "not found" cases (duplicate checking) 8. Prowlarr magnet link redirects - Some indexers return HTTP URLs that redirect to magnet: links. Fixed by intercepting 3xx redirects before axios follows them, extracting the Location header, and routing to magnet flow if target is a magnet: link 9. Category save path not updating - When user changes download_dir setting, category keeps old path. Fixed by:

  • Checking existing categories before create/edit (avoid unnecessary 409 errors)
  • Invalidating service singleton when settings change (forces config reload)
  • Settings API calls invalidateQBittorrentService() after updating paths or credentials

Tech Stack

  • axios (HTTP + cookie mgmt)
  • parse-torrent (bencode + hash extraction)
  • form-data (multipart uploads)