From a3381cba31de0a6cdf6d3587f8ff023aab7c156c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Dec 2025 18:00:48 +0000 Subject: [PATCH] Fix qBittorrent service not reloading config after settings change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- documentation/phase3/qbittorrent.md | 11 ++++++++++- src/app/api/admin/settings/download-client/route.ts | 4 ++++ src/app/api/admin/settings/paths/route.ts | 4 ++++ src/lib/integrations/qbittorrent.service.ts | 11 +++++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/documentation/phase3/qbittorrent.md b/documentation/phase3/qbittorrent.md index aaa6426..cf7212f 100644 --- a/documentation/phase3/qbittorrent.md +++ b/documentation/phase3/qbittorrent.md @@ -50,6 +50,12 @@ Free, open-source BitTorrent client with comprehensive Web API. 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) @@ -97,7 +103,10 @@ type TorrentState = 'downloading' | 'uploading' | 'stalledDL' | **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 calling both `createCategory` and `editCategory` on every torrent addition to ensure category path matches current config +**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 diff --git a/src/app/api/admin/settings/download-client/route.ts b/src/app/api/admin/settings/download-client/route.ts index 1bb86a1..43b6eef 100644 --- a/src/app/api/admin/settings/download-client/route.ts +++ b/src/app/api/admin/settings/download-client/route.ts @@ -58,6 +58,10 @@ export async function PUT(request: NextRequest) { console.log('[Admin] Download client settings updated'); + // Invalidate qBittorrent service singleton to force reload of credentials and URL + const { invalidateQBittorrentService } = await import('@/lib/integrations/qbittorrent.service'); + invalidateQBittorrentService(); + return NextResponse.json({ success: true, message: 'Download client settings updated successfully', diff --git a/src/app/api/admin/settings/paths/route.ts b/src/app/api/admin/settings/paths/route.ts index 6c48834..bc19004 100644 --- a/src/app/api/admin/settings/paths/route.ts +++ b/src/app/api/admin/settings/paths/route.ts @@ -55,6 +55,10 @@ export async function PUT(request: NextRequest) { console.log('[Admin] Paths settings updated'); + // Invalidate qBittorrent service singleton to force reload of download_dir + const { invalidateQBittorrentService } = await import('@/lib/integrations/qbittorrent.service'); + invalidateQBittorrentService(); + return NextResponse.json({ success: true, message: 'Paths settings updated successfully', diff --git a/src/lib/integrations/qbittorrent.service.ts b/src/lib/integrations/qbittorrent.service.ts index afae362..6310d86 100644 --- a/src/lib/integrations/qbittorrent.service.ts +++ b/src/lib/integrations/qbittorrent.service.ts @@ -740,6 +740,17 @@ export class QBittorrentService { let qbittorrentService: QBittorrentService | null = null; let configLoaded = false; +/** + * Invalidate the qBittorrent service singleton + * Call this after updating download_dir or qBittorrent connection settings + * Forces service to reload configuration from database on next use + */ +export function invalidateQBittorrentService(): void { + console.log('[qBittorrent] Invalidating service singleton - will reload config on next use'); + qbittorrentService = null; + configLoaded = false; +} + export async function getQBittorrentService(): Promise { // Always recreate if config hasn't been loaded successfully if (!qbittorrentService || !configLoaded) {