Add remote path mapping for qBittorrent integration

Implements remote-to-local path mapping for qBittorrent downloads, allowing the app to handle differing filesystem paths between qBittorrent and the local environment (e.g., remote seedboxes, Docker). Adds UI controls in admin settings and setup wizard, validates mapping configuration, and applies path transformation in download and import processors. Updates documentation, API routes, and data models to support the new feature. Also improves library scan logic to remove stale records and reset orphaned audiobooks and requests. Increases minimum torrent score threshold from 30 to 50 in search and ranking logic, and exposes torrent source URLs in the admin UI.
This commit is contained in:
kikootwo
2026-01-04 06:28:17 -05:00
parent d617e26c92
commit ca7cac0c88
26 changed files with 1108 additions and 75 deletions
+8 -5
View File
@@ -24,11 +24,12 @@ Indexer aggregator for searching multiple torrent/usenet indexers simultaneously
**Extended Search:** Enabled (`extended=1`) - searches title, tags, labels, and metadata fields
**Result Filtering:**
- Minimum score threshold: 30/100
- Minimum score threshold: 50/100
- Filters applied after ranking to remove poor matches
- Ensures at least basic title/author match quality
- maxResults: 100 (increased from 50 for broader search)
**Example:** "Season of Storms" → finds all "Season of Storms" torrents → ranks by author match → filters score < 30
**Example:** "Season of Storms" → finds all "Season of Storms" torrents → ranks by author match → filters score < 50
```typescript
interface TorrentResult {
@@ -66,15 +67,17 @@ interface TorrentResult {
**Manual Search** (`POST /api/requests/{id}/manual-search`)
- Triggers automatic search job for requests with status: pending, failed, awaiting_search
- Searches only enabled indexers (title only, maxResults: 100)
- Ranks all results, filters scores < 30
- Ranks all results, filters scores < 50
- Selects best torrent from filtered results
- Updates request status to 'pending'
**Interactive Search** (`POST /api/requests/{id}/interactive-search`)
- Returns ranked torrent results for user selection
- Searches only enabled indexers (title only, maxResults: 100)
- Ranks all results, filters scores < 30
- Searches only enabled indexers (title only or custom, maxResults: 100)
- Accepts optional custom search title in request body
- Ranks all results, filters scores < 50
- Shows table with: rank, title, size, quality score, seeders, indexer, publish date
- Editable title field allows search refinement
- Available for same statuses as manual search
- User clicks "Download" button to select specific torrent
+58 -1
View File
@@ -48,7 +48,12 @@ Free, open-source BitTorrent client with comprehensive Web API.
- `download_client_password` - qBittorrent password
- `download_dir` - Download save path (passed to qBittorrent for all torrents)
Validation: All fields checked before service initialization.
**Optional (Remote Path Mapping):**
- `download_client_remote_path_mapping_enabled` - Enable path mapping (boolean as string "true"/"false")
- `download_client_remote_path` - Remote path prefix from qBittorrent
- `download_client_local_path` - Local path prefix for ReadMeABook
Validation: All required fields checked before service initialization. Path mapping fields validated when enabled.
**Singleton Invalidation:**
Service uses singleton pattern for performance. When settings change (via admin settings page), singleton is invalidated to force reload:
@@ -72,6 +77,53 @@ Service uses singleton pattern for performance. When settings change (via admin
This prevents issues where category retains old save path after user changes `download_dir` setting.
## Remote Path Mapping
**Use Case:** qBittorrent runs on different machine/container with different filesystem perspective.
**Example Scenario:**
- qBittorrent reports: `/remote/mnt/d/done/Audiobook.Name`
- ReadMeABook needs: `/downloads/Audiobook.Name`
- Mapping: Remote `/remote/mnt/d/done` → Local `/downloads`
**Configuration:**
1. Admin Settings → Download Client → Enable Remote Path Mapping
2. Enter remote path (as reported by qBittorrent)
3. Enter local path (accessible to ReadMeABook)
4. Test connection validates local path exists
5. Save settings
**Implementation:**
- `PathMapper` utility (`src/lib/utils/path-mapper.ts`) handles transformation
- Applied in `monitor-download.processor.ts` when download completes
- Applied in `retry-failed-imports.processor.ts` for failed imports
- Uses simple prefix replacement with path normalization
- Graceful fallback: if path doesn't match remote prefix, returns unchanged
**Path Transformation:**
```typescript
// Input from qBittorrent
qbPath = "/remote/mnt/d/done/Audiobook.Name"
// Config
remotePath = "/remote/mnt/d/done"
localPath = "/downloads"
// Output (used for file organization)
organizePath = "/downloads/Audiobook.Name"
```
**Validation:**
- Local path accessibility checked during test connection
- Prevents misconfiguration before save
- Warning shown for existing downloads (mapping only affects new downloads)
**Behavior:**
- Mapping only applies when enabled
- If path doesn't start with remote prefix, returns original (logs warning)
- Path normalization handles trailing slashes, backslashes, redundant separators
- Works with both `content_path` and constructed `save_path + name`
## Data Models
```typescript
@@ -107,6 +159,11 @@ type TorrentState = 'downloading' | 'uploading' | 'stalledDL' |
- 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
**10. Remote seedbox path mismatch** - qBittorrent on remote machine reports different filesystem paths. Fixed by:
- Remote path mapping feature with toggle in admin settings and setup wizard
- PathMapper utility for prefix replacement transformation
- Local path validation during test connection
- Applied in download completion and import retry processors
## Tech Stack
+25 -10
View File
@@ -7,16 +7,31 @@ Evaluates and scores torrents to automatically select best audiobook download.
## Scoring Criteria (100 points max)
**1. Title/Author Match (50 pts max) - MOST IMPORTANT**
- Title matching: 0-35 pts
- Complete title match (followed by metadata: " by", " [", " -") → 35 pts
- Title is substring but continues with more words → fuzzy similarity (partial credit)
- Prevents series confusion: "The Housemaid" vs "The Housemaid's Secret"
- No exact match → fuzzy similarity (partial credit)
- Author presence: 0-15 pts
- Exact substring match → proportional credit
- No exact match → fuzzy similarity (partial credit)
- Splits authors on delimiters (comma, &, "and", " - ")
- Filters out roles ("translator", "narrator")
**Multi-Stage Matching:**
**Stage 1: Word Coverage Filter (MANDATORY)**
- Extracts significant words from request (filters stop words: "the", "a", "an", "of", "on", "in", "at", "by", "for")
- Calculates coverage: % of request words found in torrent title
- **Hard requirement: 80%+ coverage or automatic 0 score**
- Example: "The Wild Robot on the Island" → ["wild", "robot", "island"]
- "The Wild Robot" → ["wild", "robot"] → 2/3 = 67% → **REJECTED**
- "The Wild Robot on the Island" → 3/3 = 100% → **PASSES**
- Prevents wrong series books from matching
**Stage 2: Title Matching (0-35 pts)**
- Only scored if Stage 1 passes
- Complete title match (followed by metadata: " by", " [", " -") → 35 pts
- Title is substring but continues with more words → fuzzy similarity (partial credit)
- Prevents series confusion: "The Housemaid" vs "The Housemaid's Secret"
- No exact match → fuzzy similarity (partial credit)
**Stage 3: Author Matching (0-15 pts)**
- Exact substring match → proportional credit
- No exact match → fuzzy similarity (partial credit)
- Splits authors on delimiters (comma, &, "and", " - ")
- Filters out roles ("translator", "narrator")
- Order-independent, no structure assumptions
- Ensures correct book is selected over wrong book with better format