All checks were successful
When Sonarr and Radarr had the same instance ID (e.g., 'i3omb'), the Radarr retriever would overwrite the Sonarr retriever in the Map. This caused webhook refreshes to show '0 instance(s)' for Sonarr. Now uses ':' as the unique key so both can coexist.
309 lines
10 KiB
JavaScript
309 lines
10 KiB
JavaScript
// 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);
|
|
const uniqueKey = `${config.type}:${config.id}`;
|
|
this.retrievers.set(uniqueKey, retriever);
|
|
logToFile(`[ArrRetrieverRegistry] Created ${config.type} retriever: ${config.name} (${uniqueKey})`);
|
|
} 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<ArrRetriever>} 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<ArrRetriever>} Array of retriever instances
|
|
*/
|
|
getRetrieversByType(type) {
|
|
return this.getAllRetrievers().filter(retriever => retriever.getRetrieverType() === type);
|
|
},
|
|
|
|
/**
|
|
* Get tags from all retrievers
|
|
* @returns {Promise<Array<Object>>} 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<Object>>} 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<Object>>} 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<Object>} 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<Object>} 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<Object>} 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;
|