Add Transmission/NZBGet and per-client paths and much more

Extend multi-download-client support to include Transmission and NZBGet and introduce per-client custom download paths. Adds protocol mapping and new client types, Transmission/NZBGet integration services, API CRUD and validation changes, UI components/modal updates and live path previews, and manager routing by protocol. Includes DB migrations (download_path on download_history, interactive_search_access on users), schema updates, and related processor/service fixes and tests to ensure backward compatibility and proper path resolution.
This commit is contained in:
kikootwo
2026-02-09 19:45:43 -05:00
parent d7acd67aa4
commit 4b90b35748
117 changed files with 9346 additions and 1488 deletions
@@ -0,0 +1,180 @@
/**
* Component: Download Client Interface
* Documentation: documentation/phase3/download-clients.md
*
* Defines the contract all download clients must implement.
* Enables protocol-agnostic download management across torrent and usenet clients.
*/
// =========================================================================
// TYPE DEFINITIONS
// =========================================================================
/** Supported download client types — single source of truth */
export const SUPPORTED_CLIENT_TYPES = ['qbittorrent', 'sabnzbd', 'nzbget', 'transmission'] as const;
/** Identifies the specific download client software */
export type DownloadClientType = (typeof SUPPORTED_CLIENT_TYPES)[number];
/** Human-readable display names for each client type */
export const CLIENT_DISPLAY_NAMES: Record<DownloadClientType, string> = {
qbittorrent: 'qBittorrent',
sabnzbd: 'SABnzbd',
nzbget: 'NZBGet',
transmission: 'Transmission',
};
/** Get display name for a client type, falling back to the raw type */
export function getClientDisplayName(type: string): string {
return CLIENT_DISPLAY_NAMES[type as DownloadClientType] || type;
}
/** The download protocol a client operates on */
export type ProtocolType = 'torrent' | 'usenet';
/** Maps each client type to its download protocol */
export const CLIENT_PROTOCOL_MAP: Record<DownloadClientType, ProtocolType> = {
qbittorrent: 'torrent',
sabnzbd: 'usenet',
nzbget: 'usenet',
transmission: 'torrent',
};
/** Unified download status across all clients */
export type DownloadStatus =
| 'downloading'
| 'completed'
| 'seeding'
| 'paused'
| 'queued'
| 'failed'
| 'processing'
| 'checking';
// =========================================================================
// DATA INTERFACES
// =========================================================================
/**
* Unified download information returned by all clients.
* Normalizes torrent and NZB data into a single shape.
*/
export interface DownloadInfo {
/** Client-assigned identifier (torrent hash or NZB ID) */
id: string;
/** Display name of the download */
name: string;
/** Total size in bytes */
size: number;
/** Bytes downloaded so far */
bytesDownloaded: number;
/** Download progress from 0.0 to 1.0 */
progress: number;
/** Normalized download status */
status: DownloadStatus;
/** Current download speed in bytes/sec */
downloadSpeed: number;
/** Estimated time remaining in seconds */
eta: number;
/** Category/label assigned to this download */
category: string;
/** Filesystem path where download is stored (available after completion) */
downloadPath?: string;
/** When the download completed */
completedAt?: Date;
/** Error message if download failed */
errorMessage?: string;
/** Time spent seeding in seconds (torrent clients only) */
seedingTime?: number;
/** Upload/download ratio (torrent clients only) */
ratio?: number;
}
/** Options for adding a new download */
export interface AddDownloadOptions {
/** Category/label to assign */
category?: string;
/** Priority level (interpretation varies by client) */
priority?: string;
/** Whether to add in paused state */
paused?: boolean;
}
/** Result of a connection test */
export interface ConnectionTestResult {
success: boolean;
message?: string;
version?: string;
}
// =========================================================================
// DOWNLOAD CLIENT INTERFACE
// =========================================================================
/**
* IDownloadClient — the contract every download client must implement.
*
* Provides a unified API for managing downloads across different protocols
* and client software. Processors interact with this interface exclusively,
* enabling new download clients to be added without modifying consumer code.
*
* To add a new client (e.g., Transmission):
* 1. Create a service class implementing IDownloadClient
* 2. Add the type to DownloadClientType
* 3. Add factory case in DownloadClientManager
*/
export interface IDownloadClient {
/** Identifies the client software (e.g., 'qbittorrent', 'sabnzbd') */
readonly clientType: DownloadClientType;
/** The protocol this client operates on */
readonly protocol: ProtocolType;
/**
* Test the connection to the download client.
* @returns Connection test result with success/failure and optional version
*/
testConnection(): Promise<ConnectionTestResult>;
/**
* Add a new download.
* @param url - Download URL (magnet link, .torrent URL, or .nzb URL)
* @param options - Optional download settings
* @returns Client-assigned download ID (torrent hash or NZB ID)
*/
addDownload(url: string, options?: AddDownloadOptions): Promise<string>;
/**
* Get current status of a download.
* Includes retry logic for race conditions (e.g., torrent not immediately available after adding).
* @param id - Download ID returned by addDownload
* @returns Download info, or null if not found
*/
getDownload(id: string): Promise<DownloadInfo | null>;
/**
* Pause a download.
* @param id - Download ID
*/
pauseDownload(id: string): Promise<void>;
/**
* Resume a paused download.
* @param id - Download ID
*/
resumeDownload(id: string): Promise<void>;
/**
* Delete a download from the client.
* @param id - Download ID
* @param deleteFiles - Whether to also delete downloaded files (default: false)
*/
deleteDownload(id: string, deleteFiles?: boolean): Promise<void>;
/**
* Perform post-download cleanup specific to the client.
* - qBittorrent: No-op (torrents continue seeding, handled by cleanup job)
* - SABnzbd: Archives the completed NZB from history
* @param id - Download ID
*/
postProcess(id: string): Promise<void>;
}