// Copyright (c) 2026 Gordon Bolton. MIT License. const { logToFile } = require('./logger'); const { getSonarrInstances, getRadarrInstances } = require('./config'); // Import retriever classes const PollingSonarrRetriever = require('../clients/PollingSonarrRetriever'); const PollingRadarrRetriever = require('../clients/PollingRadarrRetriever'); // Retriever type mapping const retrieverClasses = { sonarr: PollingSonarrRetriever, radarr: PollingRadarrRetriever }; /** * Singleton registry for *arr data retrievers */ const arrRetrieverRegistry = { retrievers: new Map(), initialized: false, /** * Initialize all configured *arr retrievers */ async initialize() { if (this.initialized) { return; } logToFile('[ArrRetrieverRegistry] Initializing *arr retrievers...'); // Get all instance configurations const sonarrInstances = getSonarrInstances(); const radarrInstances = getRadarrInstances(); // Create retriever instances const instanceConfigs = [ ...sonarrInstances.map(inst => ({ ...inst, type: 'sonarr' })), ...radarrInstances.map(inst => ({ ...inst, type: 'radarr' })) ]; for (const config of instanceConfigs) { try { const RetrieverClass = retrieverClasses[config.type]; if (!RetrieverClass) { logToFile(`[ArrRetrieverRegistry] Unknown retriever type: ${config.type}`); continue; } const retriever = new RetrieverClass(config); this.retrievers.set(config.id, retriever); logToFile(`[ArrRetrieverRegistry] Created ${config.type} retriever: ${config.name} (${config.id})`); } catch (error) { logToFile(`[ArrRetrieverRegistry] Failed to create retriever ${config.id}: ${error.message}`); } } this.initialized = true; logToFile(`[ArrRetrieverRegistry] Initialized ${this.retrievers.size} *arr retrievers`); }, /** * Get all registered retrievers * @returns {Array} Array of retriever instances */ getAllRetrievers() { return Array.from(this.retrievers.values()); }, /** * Get retriever by instance ID * @param {string} instanceId - The instance ID * @returns {ArrRetriever|null} Retriever instance or null if not found */ getRetriever(instanceId) { return this.retrievers.get(instanceId) || null; }, /** * Get retrievers by type * @param {string} type - Retriever type ('sonarr', 'radarr') * @returns {Array} Array of retriever instances */ getRetrieversByType(type) { return this.getAllRetrievers().filter(retriever => retriever.getRetrieverType() === type); }, /** * Get tags from all retrievers * @returns {Promise>} Array of tag results with instance info */ async getAllTags() { const retrievers = this.getAllRetrievers(); if (retrievers.length === 0) { return []; } // Fetch tags from all retrievers in parallel const results = await Promise.allSettled( retrievers.map(async (retriever) => { try { const tags = await retriever.getTags(); logToFile(`[ArrRetrieverRegistry] ${retriever.name}: ${tags.length} tags`); return { instance: retriever.getInstanceId(), data: tags }; } catch (error) { logToFile(`[ArrRetrieverRegistry] Error fetching tags from ${retriever.name}: ${error.message}`); return { instance: retriever.getInstanceId(), data: [] }; } }) ); return results .filter(result => result.status === 'fulfilled') .map(result => result.value); }, /** * Get queue from all retrievers * @returns {Promise>} Array of queue results with instance info */ async getAllQueues() { const retrievers = this.getAllRetrievers(); if (retrievers.length === 0) { return []; } // Fetch queues from all retrievers in parallel const results = await Promise.allSettled( retrievers.map(async (retriever) => { try { const queue = await retriever.getQueue(); logToFile(`[ArrRetrieverRegistry] ${retriever.name}: ${(queue.records || []).length} queue items`); return { instance: retriever.getInstanceId(), data: queue }; } catch (error) { logToFile(`[ArrRetrieverRegistry] Error fetching queue from ${retriever.name}: ${error.message}`); return { instance: retriever.getInstanceId(), data: { records: [] } }; } }) ); return results .filter(result => result.status === 'fulfilled') .map(result => result.value); }, /** * Get history from all retrievers * @param {Object} options - Optional parameters for history fetch * @returns {Promise>} Array of history results with instance info */ async getAllHistory(options = {}) { const retrievers = this.getAllRetrievers(); if (retrievers.length === 0) { return []; } // Fetch history from all retrievers in parallel const results = await Promise.allSettled( retrievers.map(async (retriever) => { try { const history = await retriever.getHistory(options); logToFile(`[ArrRetrieverRegistry] ${retriever.name}: ${(history.records || []).length} history records`); return { instance: retriever.getInstanceId(), data: history }; } catch (error) { logToFile(`[ArrRetrieverRegistry] Error fetching history from ${retriever.name}: ${error.message}`); return { instance: retriever.getInstanceId(), data: { records: [] } }; } }) ); return results .filter(result => result.status === 'fulfilled') .map(result => result.value); }, /** * Get tags grouped by retriever type * @returns {Promise} Tags grouped by retriever type (array of { instance, data } objects) */ async getTagsByType() { const sonarrRetrievers = this.getRetrieversByType('sonarr'); const radarrRetrievers = this.getRetrieversByType('radarr'); const sonarrTags = await Promise.allSettled( sonarrRetrievers.map(async (retriever) => { try { const tags = await retriever.getTags(); return { instance: retriever.getInstanceId(), data: tags }; } catch (error) { logToFile(`[ArrRetrieverRegistry] Error fetching tags from ${retriever.name}: ${error.message}`); return { instance: retriever.getInstanceId(), data: [] }; } }) ); const radarrTags = await Promise.allSettled( radarrRetrievers.map(async (retriever) => { try { const tags = await retriever.getTags(); return { instance: retriever.getInstanceId(), data: tags }; } catch (error) { logToFile(`[ArrRetrieverRegistry] Error fetching tags from ${retriever.name}: ${error.message}`); return { instance: retriever.getInstanceId(), data: [] }; } }) ); return { sonarr: sonarrTags .filter(result => result.status === 'fulfilled') .map(result => result.value), radarr: radarrTags .filter(result => result.status === 'fulfilled') .map(result => result.value) }; }, /** * Get queue grouped by retriever type * @returns {Promise} Queue grouped by retriever type */ async getQueuesByType() { const sonarrRetrievers = this.getRetrieversByType('sonarr'); const radarrRetrievers = this.getRetrieversByType('radarr'); const sonarrQueues = await Promise.allSettled( sonarrRetrievers.map(async (retriever) => { try { const queue = await retriever.getQueue(); return { instance: retriever.getInstanceId(), data: queue }; } catch (error) { logToFile(`[ArrRetrieverRegistry] Error fetching queue from ${retriever.name}: ${error.message}`); return { instance: retriever.getInstanceId(), data: { records: [] } }; } }) ); const radarrQueues = await Promise.allSettled( radarrRetrievers.map(async (retriever) => { try { const queue = await retriever.getQueue(); return { instance: retriever.getInstanceId(), data: queue }; } catch (error) { logToFile(`[ArrRetrieverRegistry] Error fetching queue from ${retriever.name}: ${error.message}`); return { instance: retriever.getInstanceId(), data: { records: [] } }; } }) ); return { sonarr: sonarrQueues .filter(result => result.status === 'fulfilled') .map(result => result.value), radarr: radarrQueues .filter(result => result.status === 'fulfilled') .map(result => result.value) }; }, /** * Get history grouped by retriever type * @param {Object} options - Optional parameters for history fetch * @returns {Promise} History grouped by retriever type */ async getHistoryByType(options = {}) { const sonarrRetrievers = this.getRetrieversByType('sonarr'); const radarrRetrievers = this.getRetrieversByType('radarr'); const sonarrHistory = await Promise.allSettled( sonarrRetrievers.map(async (retriever) => { try { const history = await retriever.getHistory(options); return { instance: retriever.getInstanceId(), data: history }; } catch (error) { logToFile(`[ArrRetrieverRegistry] Error fetching history from ${retriever.name}: ${error.message}`); return { instance: retriever.getInstanceId(), data: { records: [] } }; } }) ); const radarrHistory = await Promise.allSettled( radarrRetrievers.map(async (retriever) => { try { const history = await retriever.getHistory(options); return { instance: retriever.getInstanceId(), data: history }; } catch (error) { logToFile(`[ArrRetrieverRegistry] Error fetching history from ${retriever.name}: ${error.message}`); return { instance: retriever.getInstanceId(), data: { records: [] } }; } }) ); return { sonarr: sonarrHistory .filter(result => result.status === 'fulfilled') .map(result => result.value), radarr: radarrHistory .filter(result => result.status === 'fulfilled') .map(result => result.value) }; } }; module.exports = arrRetrieverRegistry;