ecaedbaf6a
Build and Push Docker Image / build (push) Successful in 50s
Docs Check / Markdown lint (push) Successful in 1m11s
CI / Security audit (push) Successful in 2m1s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 1m23s
CI / Tests & coverage (push) Successful in 2m30s
Docs Check / Mermaid diagram parse check (push) Successful in 2m9s
CI / Swagger Validation & Coverage (push) Successful in 4m2s
The buildUserDownloads function was calling async matcher functions without awaiting them, causing Promise objects to be returned instead of resolved arrays. This resulted in empty download lists and 17 failing tests. - Made buildUserDownloads async - Added await to matchSabSlots, matchSabHistory, and matchTorrents calls - Updated unit tests to await buildUserDownloads calls All 759 tests now pass.
117 lines
4.6 KiB
JavaScript
117 lines
4.6 KiB
JavaScript
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
|
|
|
/**
|
|
* DownloadBuilder - Aggregates and matches download data from multiple sources.
|
|
* This service processes cached data from SABnzbd, qBittorrent, Sonarr, and Radarr to build
|
|
* a unified view of downloads for each user, matching downloads to media metadata via tags.
|
|
*/
|
|
|
|
const DownloadMatcher = require('./DownloadMatcher');
|
|
|
|
/**
|
|
* Builds a unified list of downloads for a user from multiple download clients.
|
|
* Matches SABnzbd and qBittorrent downloads to Sonarr/Radarr activity using tags.
|
|
* @param {Object} cacheSnapshot - Cached data from all services
|
|
* @param {Object} options - User context and metadata maps
|
|
* @param {string} options.username - Lowercase username for tag matching
|
|
* @param {string} options.usernameSanitized - Original username
|
|
* @param {boolean} options.isAdmin - Whether user is admin
|
|
* @param {boolean} options.showAll - Whether to show all users' downloads
|
|
* @param {Map} options.seriesMap - Map of seriesId to series object
|
|
* @param {Map} options.moviesMap - Map of movieId to movie object
|
|
* @param {Map} options.sonarrTagMap - Map of Sonarr tag IDs to labels
|
|
* @param {Map} options.radarrTagMap - Map of Radarr tag IDs to labels
|
|
* @param {Map} options.embyUserMap - Map of Emby users for admin view
|
|
* @param {OmbiRetriever} options.ombiRetriever - Ombi data retriever instance (optional)
|
|
* @param {string} options.ombiBaseUrl - Ombi base URL for link generation (optional)
|
|
* @returns {Array} Array of download objects for the user
|
|
*/
|
|
async function buildUserDownloads(cacheSnapshot, { username, usernameSanitized, isAdmin, showAll, seriesMap, moviesMap, sonarrTagMap, radarrTagMap, embyUserMap, ombiRetriever, ombiBaseUrl }) {
|
|
// Input validation
|
|
if (!cacheSnapshot || typeof cacheSnapshot !== 'object') {
|
|
console.error('[DownloadBuilder] Invalid cacheSnapshot provided');
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
// Handle null/undefined cache data
|
|
const sabnzbdQueue = cacheSnapshot.sabnzbdQueue || { data: { queue: { slots: [] } } };
|
|
const sabnzbdHistory = cacheSnapshot.sabnzbdHistory || { data: { history: { slots: [] } } };
|
|
const sonarrQueue = cacheSnapshot.sonarrQueue || { data: { records: [] } };
|
|
const sonarrHistory = cacheSnapshot.sonarrHistory || { data: { records: [] } };
|
|
const radarrQueue = cacheSnapshot.radarrQueue || { data: { records: [] } };
|
|
const radarrHistory = cacheSnapshot.radarrHistory || { data: { records: [] } };
|
|
const qbittorrentTorrents = cacheSnapshot.qbittorrentTorrents || [];
|
|
|
|
// Get queue status for SABnzbd
|
|
const queueStatus = sabnzbdQueue.data?.queue?.status || null;
|
|
const queueSpeed = sabnzbdQueue.data?.queue?.speed || null;
|
|
const queueKbpersec = sabnzbdQueue.data?.queue?.kbpersec || null;
|
|
|
|
// Build context for matching functions
|
|
const context = {
|
|
sonarrQueueRecords: sonarrQueue.data?.records || [],
|
|
sonarrHistoryRecords: sonarrHistory.data?.records || [],
|
|
radarrQueueRecords: radarrQueue.data?.records || [],
|
|
radarrHistoryRecords: radarrHistory.data?.records || [],
|
|
seriesMap: seriesMap || new Map(),
|
|
moviesMap: moviesMap || new Map(),
|
|
sonarrTagMap: sonarrTagMap || new Map(),
|
|
radarrTagMap: radarrTagMap || new Map(),
|
|
username,
|
|
isAdmin,
|
|
showAll,
|
|
embyUserMap: embyUserMap || new Map(),
|
|
queueStatus,
|
|
queueSpeed,
|
|
queueKbpersec,
|
|
ombiRetriever,
|
|
ombiBaseUrl
|
|
};
|
|
|
|
// Match all download sources
|
|
const userDownloads = [];
|
|
const seenDownloadKeys = new Set();
|
|
|
|
if (sabnzbdQueue.data?.queue?.slots) {
|
|
const sabMatches = await DownloadMatcher.matchSabSlots(sabnzbdQueue.data.queue.slots, context);
|
|
for (const dl of sabMatches) {
|
|
const key = `${dl.type}:${dl.title}`;
|
|
if (!seenDownloadKeys.has(key)) {
|
|
seenDownloadKeys.add(key);
|
|
userDownloads.push(dl);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sabnzbdHistory.data?.history?.slots) {
|
|
const sabHistoryMatches = await DownloadMatcher.matchSabHistory(sabnzbdHistory.data.history.slots, context);
|
|
for (const dl of sabHistoryMatches) {
|
|
const key = `${dl.type}:${dl.title}`;
|
|
if (!seenDownloadKeys.has(key)) {
|
|
seenDownloadKeys.add(key);
|
|
userDownloads.push(dl);
|
|
}
|
|
}
|
|
}
|
|
|
|
const torrentMatches = await DownloadMatcher.matchTorrents(qbittorrentTorrents, context);
|
|
for (const dl of torrentMatches) {
|
|
const key = `${dl.type}:${dl.title}`;
|
|
if (!seenDownloadKeys.has(key)) {
|
|
seenDownloadKeys.add(key);
|
|
userDownloads.push(dl);
|
|
}
|
|
}
|
|
|
|
return userDownloads;
|
|
} catch (error) {
|
|
console.error('[DownloadBuilder] Error building user downloads:', error.message);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
buildUserDownloads
|
|
};
|