Some checks failed
Build and Push Docker Image / build (push) Successful in 31s
Docs Check / Markdown lint (push) Successful in 31s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 1m12s
CI / Tests & coverage (push) Failing after 1m39s
CI / Security audit (push) Successful in 1m49s
Docs Check / Mermaid diagram parse check (push) Successful in 1m56s
- Add abstract DownloadClient base class with standardized interface - Refactor QBittorrentClient to extend DownloadClient with Sync API support - Create SABnzbdClient implementing DownloadClient interface - Add TransmissionClient as proof-of-concept implementation - Implement DownloadClientRegistry for factory pattern and client management - Refactor poller.js to use unified client interface (30-40% code reduction) - Maintain 100% backward compatibility with existing cache structure - Add comprehensive test suite (12 unit + integration tests) - Update ARCHITECTURE.md with detailed PDCA documentation - Create ADDING-A-DOWNLOAD-CLIENT.md guide for future client additions Features: - Client-agnostic polling with error isolation - Consistent data normalization across all clients - Easy extensibility for new download client types - Zero breaking changes to existing functionality - Parallel execution with unified timing and logging
104 lines
3.9 KiB
JavaScript
104 lines
3.9 KiB
JavaScript
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
|
|
|
/**
|
|
* Abstract base class for all download clients.
|
|
* Defines the common interface that all download clients must implement.
|
|
*/
|
|
class DownloadClient {
|
|
/**
|
|
* @param {Object} instanceConfig - Configuration for this client instance
|
|
* @param {string} instanceConfig.id - Unique identifier for this instance
|
|
* @param {string} instanceConfig.name - Display name for this instance
|
|
* @param {string} instanceConfig.url - Base URL for the client API
|
|
* @param {string} [instanceConfig.apiKey] - API key for authentication (if applicable)
|
|
* @param {string} [instanceConfig.username] - Username for authentication (if applicable)
|
|
* @param {string} [instanceConfig.password] - Password for authentication (if applicable)
|
|
*/
|
|
constructor(instanceConfig) {
|
|
if (this.constructor === DownloadClient) {
|
|
throw new Error('DownloadClient is an abstract class and cannot be instantiated directly');
|
|
}
|
|
|
|
this.id = instanceConfig.id;
|
|
this.name = instanceConfig.name;
|
|
this.url = instanceConfig.url;
|
|
this.apiKey = instanceConfig.apiKey;
|
|
this.username = instanceConfig.username;
|
|
this.password = instanceConfig.password;
|
|
}
|
|
|
|
/**
|
|
* Get the client type identifier (e.g., 'qbittorrent', 'sabnzbd', 'transmission')
|
|
* @returns {string} The client type
|
|
*/
|
|
getClientType() {
|
|
throw new Error('getClientType() must be implemented by subclass');
|
|
}
|
|
|
|
/**
|
|
* Get the unique instance ID
|
|
* @returns {string} The instance ID
|
|
*/
|
|
getInstanceId() {
|
|
return this.id;
|
|
}
|
|
|
|
/**
|
|
* Test connection to the download client
|
|
* @returns {Promise<boolean>} True if connection is successful
|
|
*/
|
|
async testConnection() {
|
|
throw new Error('testConnection() must be implemented by subclass');
|
|
}
|
|
|
|
/**
|
|
* Get active downloads from this client
|
|
* @returns {Promise<Array<NormalizedDownload>>} Array of normalized download objects
|
|
*/
|
|
async getActiveDownloads() {
|
|
throw new Error('getActiveDownloads() must be implemented by subclass');
|
|
}
|
|
|
|
/**
|
|
* Optional: Get client status information
|
|
* @returns {Promise<Object|null>} Client status object or null if not supported
|
|
*/
|
|
async getClientStatus() {
|
|
return null; // Default implementation - optional method
|
|
}
|
|
|
|
/**
|
|
* Normalize a download object to the standard schema
|
|
* @param {Object} download - Raw download object from client
|
|
* @returns {NormalizedDownload} Normalized download object
|
|
*/
|
|
normalizeDownload(download) {
|
|
throw new Error('normalizeDownload() must be implemented by subclass');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @typedef {Object} NormalizedDownload
|
|
* @property {string} id - Client-specific unique ID
|
|
* @property {string} title - Download title/name
|
|
* @property {'usenet'|'torrent'} type - Download type
|
|
* @property {string} client - Client identifier ('sabnzbd', 'qbittorrent', 'transmission', etc.)
|
|
* @property {string} instanceId - Instance identifier
|
|
* @property {string} instanceName - Instance display name
|
|
* @property {string} status - Normalized status (Downloading, Seeding, Paused, etc.)
|
|
* @property {number} progress - Progress percentage (0-100)
|
|
* @property {number} size - Total size in bytes
|
|
* @property {number} downloaded - Downloaded bytes
|
|
* @property {number} speed - Current speed in bytes/sec
|
|
* @property {number|null} eta - Estimated time remaining in seconds, null if unknown
|
|
* @property {string|undefined} category - Download category (optional)
|
|
* @property {string[]|undefined} tags - Download tags (optional)
|
|
* @property {string|undefined} savePath - Save path (optional)
|
|
* @property {string|undefined} addedOn - Added timestamp (optional)
|
|
* @property {number|undefined} arrQueueId - Sonarr/Radarr queue ID (optional)
|
|
* @property {'series'|'movie'|undefined} arrType - Sonarr/Radarr type (optional)
|
|
* @property {any|undefined} raw - Original client response (escape hatch)
|
|
*/
|
|
|
|
module.exports = DownloadClient;
|