mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-03 04:40:09 +00:00
SABnzbd path mapping + ASIN-based request deletion
Add bidirectional path mapping and complete_dir-aware category sync to the SABnzbd integration. Introduces PathMapper usage, complete_dir extraction, calculateCategoryPath(), and ensureCategory() logic to choose empty/relative/absolute category paths; ensureCategory is invoked before adding NZBs. Update singleton factory to load download_dir and path-mapping config from DownloadClientManager and recreate the service when config is not loaded. Make DownloadClientManager pass path-mapping config into the SABnzbd service. Change request deletion to remove plex_library records by ASIN (deleteMany) with a fallback to exact title/author matches so availability checks and deletions are consistent. Update documentation and tests to reflect the new behavior and APIs.
This commit is contained in:
@@ -100,9 +100,20 @@ model Request {
|
||||
- Queries plex_library table to get plexRatingKey from audiobook's plexGuid
|
||||
- Calls Plex DELETE `/library/metadata/{ratingKey}` endpoint with the ratingKey
|
||||
- Requires deletion enabled in Plex: Settings > Server > Library
|
||||
- Also clears plex_library cache records
|
||||
|
||||
5. **Soft Delete Request**
|
||||
5. **Delete plex_library Cache Records**
|
||||
- **Primary:** Delete by ASIN (same query as availability check)
|
||||
- `WHERE asin = audiobookAsin OR plexGuid CONTAINS audiobookAsin`
|
||||
- Ensures exact same record found during availability check gets deleted
|
||||
- **Fallback:** Delete by exact title/author (for legacy records without ASIN)
|
||||
- Only used if ASIN-based deletion finds no records
|
||||
- **Result:** Book immediately shows as NOT available, can be re-requested
|
||||
|
||||
6. **Clear Audiobook Linkage**
|
||||
- Reset audiobook.status to 'requested'
|
||||
- Clear plexGuid (Plex mode) or absItemId (ABS mode)
|
||||
|
||||
7. **Soft Delete Request**
|
||||
- UPDATE: `deletedAt = NOW(), deletedBy = adminUserId`
|
||||
- Preserves for audit trail and orphaned download tracking
|
||||
|
||||
@@ -201,6 +212,17 @@ where: {
|
||||
11. ✅ **Plex library item deletion fails** - Log error, continue with soft delete
|
||||
12. ✅ **No plexGuid present** - Skip Plex deletion (not yet in library)
|
||||
13. ✅ **Plex deletion not enabled in settings** - Log error, continue with soft delete
|
||||
14. ✅ **Title mismatch in plex_library** - ASIN-based deletion handles title variations (e.g., "(Unabridged)" suffix)
|
||||
15. ✅ **No ASIN available** - Falls back to exact title/author matching
|
||||
|
||||
## Fixed Issues ✅
|
||||
|
||||
**1. Book Shows "Available" After Deletion Until Library Scan**
|
||||
- **Issue:** Deleted books remained "available" until the next library scan
|
||||
- **Cause:** plex_library deletion used title/author matching, but availability check used ASIN matching
|
||||
- **Impact:** Title variations (e.g., "Book Title" vs "Book Title (Unabridged)") caused plex_library records to persist
|
||||
- **Fix:** Changed plex_library deletion to use ASIN-based matching (same as availability check)
|
||||
- **Result:** Books immediately show as NOT available after deletion, can be re-requested right away
|
||||
|
||||
## File Structure
|
||||
|
||||
|
||||
@@ -65,9 +65,18 @@ Service uses singleton pattern. When settings change, singleton invalidated to f
|
||||
**Category:** `readmeabook` (auto-created for all downloads)
|
||||
|
||||
**Save Path Synchronization:**
|
||||
- Category created on first download if not exists
|
||||
- Category path set to `download_dir` config value
|
||||
- Unlike qBittorrent, SABnzbd categories are less frequently updated (set once at creation)
|
||||
- Category created/updated on every download (matches qBittorrent behavior)
|
||||
- Fetches SABnzbd's `complete_dir` setting via API to understand download location
|
||||
- Applies remote path mapping to translate RMAB's `download_dir` to SABnzbd's perspective
|
||||
- Calculates optimal category path (relative, absolute, or root)
|
||||
|
||||
**Smart Path Calculation:**
|
||||
1. Get SABnzbd's `complete_dir` from `misc.complete_dir` config
|
||||
2. Apply `PathMapper.reverseTransform()` to RMAB's `download_dir`
|
||||
3. Compare transformed path to `complete_dir`:
|
||||
- **Match:** Use empty string (downloads go to complete_dir root)
|
||||
- **Subdirectory:** Use relative path (e.g., `audiobooks`)
|
||||
- **Different:** Use absolute path (e.g., `/mnt/media/audiobooks`)
|
||||
|
||||
## Post-Processing
|
||||
|
||||
@@ -121,6 +130,12 @@ interface HistoryItem {
|
||||
completedTimestamp: string; // Unix timestamp
|
||||
downloadTime: string; // Seconds
|
||||
}
|
||||
|
||||
interface SABnzbdConfig {
|
||||
version: string;
|
||||
completeDir: string; // SABnzbd's configured complete download folder
|
||||
categories: Array<{ name: string; dir: string }>;
|
||||
}
|
||||
```
|
||||
|
||||
## NZB ID vs Torrent Hash
|
||||
@@ -168,11 +183,40 @@ interface HistoryItem {
|
||||
**Use Case:** SABnzbd runs on different machine/container with different filesystem perspective.
|
||||
|
||||
**Example Scenario:**
|
||||
- SABnzbd reports: `/remote/usenet/complete/Audiobook.Name`
|
||||
- ReadMeABook needs: `/downloads/Audiobook.Name`
|
||||
- Mapping: Remote `/remote/usenet/complete` → Local `/downloads`
|
||||
- SABnzbd sees: `/mnt/usenet/complete`
|
||||
- ReadMeABook sees: `/downloads`
|
||||
- Mapping: Remote `/mnt/usenet/complete` ↔ Local `/downloads`
|
||||
|
||||
**Implementation:** Same as qBittorrent (uses `PathMapper` utility)
|
||||
**Bidirectional Path Mapping:**
|
||||
|
||||
**1. Outgoing (RMAB → SABnzbd):** When setting category path
|
||||
- RMAB's download path: `/downloads`
|
||||
- Translated to SABnzbd's path: `/mnt/usenet/complete`
|
||||
- Applied in `sabnzbd.service.ts` via `PathMapper.reverseTransform()`
|
||||
- Combined with `complete_dir` detection for optimal category configuration
|
||||
|
||||
**2. Incoming (SABnzbd → RMAB):** When processing completed downloads
|
||||
- SABnzbd reports: `/mnt/usenet/complete/Audiobook.Name`
|
||||
- Translated to RMAB's path: `/downloads/Audiobook.Name`
|
||||
- Applied in `monitor-download.processor.ts` via `PathMapper.transform()`
|
||||
- Ensures RMAB can find and organize files
|
||||
|
||||
**Path Transformation Examples:**
|
||||
```typescript
|
||||
// Outgoing: RMAB → SABnzbd (when setting category)
|
||||
localPath = "/downloads"
|
||||
config = { remotePath: "/mnt/usenet/complete", localPath: "/downloads" }
|
||||
remotePath = PathMapper.reverseTransform(localPath, config)
|
||||
// Result: "/mnt/usenet/complete"
|
||||
|
||||
// Incoming: SABnzbd → RMAB (when processing completion)
|
||||
sabPath = "/mnt/usenet/complete/Audiobook.Name"
|
||||
config = { remotePath: "/mnt/usenet/complete", localPath: "/downloads" }
|
||||
organizePath = PathMapper.transform(sabPath, config)
|
||||
// Result: "/downloads/Audiobook.Name"
|
||||
```
|
||||
|
||||
**Implementation:** Uses `PathMapper` utility (same as qBittorrent)
|
||||
|
||||
## Fixed Issues ✅
|
||||
|
||||
@@ -181,6 +225,16 @@ interface HistoryItem {
|
||||
**3. Post-Processing Tracking** - Monitors extracting/repairing states
|
||||
**4. Queue vs History Logic** - Checks queue first, falls back to history
|
||||
**5. SSL Certificate Errors** - Optional SSL verification disable for self-signed certs
|
||||
**6. Category path not synced with complete_dir** - SABnzbd downloads to its own `complete_dir`, not RMAB's path. Fixed by:
|
||||
- Fetching SABnzbd's `complete_dir` from config API (`misc.complete_dir`)
|
||||
- Calculating relative or absolute category path based on path comparison
|
||||
- Applying remote path mapping before comparison
|
||||
- Syncing category path on every download (same as qBittorrent)
|
||||
**7. Remote path mapping not applied** - Paths weren't translated between RMAB and SABnzbd perspectives. Fixed by:
|
||||
- Adding `PathMappingConfig` to SABnzbd service constructor
|
||||
- Applying `reverseTransform()` when setting category path (outgoing)
|
||||
- Applying `transform()` when processing completed downloads (incoming)
|
||||
- Using same `PathMapper` utility as qBittorrent for consistency
|
||||
|
||||
## Automatic Cleanup
|
||||
|
||||
@@ -223,9 +277,11 @@ interface HistoryItem {
|
||||
| ID Format | NZB ID (immediate) | Torrent hash (extracted) |
|
||||
| Post-Processing | Automatic (par2, extraction) | None (manual) |
|
||||
| Seeding | N/A (Usenet is not P2P) | Required (tracker) |
|
||||
| Categories | Path-based | Path + tag-based |
|
||||
| Categories | Path-based (relative to complete_dir) | Path + tag-based |
|
||||
| File Handling | Auto-extracts archives | Downloads as-is |
|
||||
| Cleanup | Automatic (optional, per-indexer) | Seeding time based |
|
||||
| Path Mapping | ✅ Bidirectional (same as qBit) | ✅ Bidirectional |
|
||||
| Category Sync | ✅ Every download | ✅ Every download |
|
||||
|
||||
## Tech Stack
|
||||
|
||||
|
||||
Reference in New Issue
Block a user