Fix qBittorrent category save path not updating when config changes

Previously, when a user changed the download_dir setting after initial
setup, the qBittorrent category "readmeabook" would retain the old save
path. This could cause torrents to download to the wrong location,
depending on qBittorrent's Automatic Torrent Management (ATM) settings.

Root cause:
- ensureCategory() only created the category if it didn't exist
- createCategory API is idempotent but doesn't update existing categories
- If download_dir changed from /downloads to /downloads/RMAB, category
  would still have savePath=/downloads

qBittorrent behavior:
- If ATM enabled: category savePath overrides per-torrent savepath
- If ATM disabled: per-torrent savepath takes precedence

Fix:
- ensureCategory() now calls both createCategory AND editCategory
- createCategory: ensures category exists (idempotent)
- editCategory: updates save path to match current download_dir config
- This guarantees category path is always synced with database config

Benefits:
- Users can change download_dir setting and it takes effect immediately
- Works regardless of ATM settings in qBittorrent
- No manual qBittorrent category management needed

Updated documentation/phase3/qbittorrent.md to explain category
management and save path synchronization.
This commit is contained in:
Claude
2025-12-22 00:18:21 +00:00
committed by kikootwo
parent ef98dcf438
commit 5188fe1727
2 changed files with 53 additions and 10 deletions
+25 -6
View File
@@ -30,24 +30,42 @@ Free, open-source BitTorrent client with comprehensive Web API.
**Auth:** Cookie-based (login required) **Auth:** Cookie-based (login required)
**POST /auth/login** - Get session cookie **POST /auth/login** - Get session cookie
**POST /torrents/add** - Add torrent (supports `urls` and `torrents` params) **POST /torrents/add** - Add torrent (supports `urls` and `torrents` params, `savepath` override)
**GET /torrents/info?hashes={hash}** - Get status/progress **GET /torrents/info?hashes={hash}** - Get status/progress
**POST /torrents/pause** - Pause torrent **POST /torrents/pause** - Pause torrent
**POST /torrents/resume** - Resume **POST /torrents/resume** - Resume
**POST /torrents/delete** - Delete torrent **POST /torrents/delete** - Delete torrent
**GET /torrents/files** - Get file list **GET /torrents/files** - Get file list
**POST /torrents/setCategory** - Set category **POST /torrents/createCategory** - Create category with save path
**POST /torrents/editCategory** - Update category save path
**POST /torrents/setCategory** - Set category for torrent
## Config ## Config
**Required (database only, no env fallbacks):** **Required (database only, no env fallbacks):**
- `qbittorrent_url` - `download_client_url` - qBittorrent Web UI URL
- `qbittorrent_username` - `download_client_username` - qBittorrent username
- `qbittorrent_password` - `download_client_password` - qBittorrent password
- `paths_downloads` - `download_dir` - Download save path (passed to qBittorrent for all torrents)
Validation: All fields checked before service initialization. Validation: All fields checked before service initialization.
## 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 ## Data Models
```typescript ```typescript
@@ -79,6 +97,7 @@ type TorrentState = 'downloading' | 'uploading' | 'stalledDL' |
**6. Race condition on torrent availability** - Fixed with 3s initial delay + exponential backoff retry (500ms, 1s, 2s) **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) **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 **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 calling both `createCategory` and `editCategory` on every torrent addition to ensure category path matches current config
## Tech Stack ## Tech Stack
+28 -4
View File
@@ -369,7 +369,8 @@ export class QBittorrentService {
} }
/** /**
* Ensure category exists in qBittorrent * Ensure category exists in qBittorrent with correct save path
* Always updates the category's save path to match current config
*/ */
private async ensureCategory(category: string): Promise<void> { private async ensureCategory(category: string): Promise<void> {
if (!this.cookie) { if (!this.cookie) {
@@ -377,7 +378,7 @@ export class QBittorrentService {
} }
try { try {
// Create category (this is idempotent - won't fail if it already exists) // Try to create category first (idempotent - won't fail if exists)
await this.client.post( await this.client.post(
'/torrents/createCategory', '/torrents/createCategory',
new URLSearchParams({ new URLSearchParams({
@@ -392,11 +393,34 @@ export class QBittorrentService {
} }
); );
console.log(`[qBittorrent] Category "${category}" ensured`); console.log(`[qBittorrent] Category "${category}" created/exists`);
} catch (error) { } catch (error) {
// Ignore errors - category might already exist // Category might already exist - that's OK
console.log(`[qBittorrent] Category creation returned:`, error); console.log(`[qBittorrent] Category creation returned:`, error);
} }
// Always update the category's save path to ensure it matches current config
// This handles cases where download_dir was changed after category was created
try {
await this.client.post(
'/torrents/editCategory',
new URLSearchParams({
category,
savePath: this.defaultSavePath,
}),
{
headers: {
Cookie: this.cookie,
'Content-Type': 'application/x-www-form-urlencoded',
},
}
);
console.log(`[qBittorrent] Category "${category}" save path updated to: ${this.defaultSavePath}`);
} catch (error) {
console.warn(`[qBittorrent] Failed to update category save path:`, error);
// Don't throw - torrents can still be added with per-torrent savepath parameter
}
} }
/** /**