From 44901b3e4f5f5ce550e150b1118172dbff8bf783 Mon Sep 17 00:00:00 2001 From: Brandon Cohen Date: Tue, 5 Dec 2023 17:32:58 -0800 Subject: [PATCH] refactor: updated to reflect latest availability sync changes --- server/lib/availabilitySync.ts | 299 ++++++++++++--------------------- 1 file changed, 108 insertions(+), 191 deletions(-) diff --git a/server/lib/availabilitySync.ts b/server/lib/availabilitySync.ts index cfe02565..f016a852 100644 --- a/server/lib/availabilitySync.ts +++ b/server/lib/availabilitySync.ts @@ -8,7 +8,6 @@ import { getRepository } from '@server/datasource'; import Media from '@server/entity/Media'; import MediaRequest from '@server/entity/MediaRequest'; import type Season from '@server/entity/Season'; -import SeasonRequest from '@server/entity/SeasonRequest'; import { User } from '@server/entity/User'; import type { RadarrSettings, SonarrSettings } from '@server/lib/settings'; import { getSettings } from '@server/lib/settings'; @@ -203,9 +202,6 @@ class AvailabilitySync { await this.mediaUpdater(media, true); } } - if (!mediaExists) { - await mediaRepository.save({ media, ...media }); - } } } catch (ex) { logger.error('Failed to complete availability sync.', { @@ -246,87 +242,55 @@ class AvailabilitySync { } while (mediaPage.length > 0); } - private findMediaStatus( - requests: MediaRequest[], - is4k: boolean - ): MediaStatus { - const filteredRequests = requests.filter( - (request) => request.is4k === is4k - ); - - let mediaStatus: MediaStatus; - - if ( - filteredRequests.some( - (request) => request.status === MediaRequestStatus.APPROVED - ) - ) { - mediaStatus = MediaStatus.PROCESSING; - } else if ( - filteredRequests.some( - (request) => request.status === MediaRequestStatus.PENDING - ) - ) { - mediaStatus = MediaStatus.PENDING; - } else { - mediaStatus = MediaStatus.UNKNOWN; - } - - return mediaStatus; - } - private async mediaUpdater(media: Media, is4k: boolean): Promise { const mediaRepository = getRepository(Media); - const requestRepository = getRepository(MediaRequest); try { - // Find all related requests only if - // the related media has an available status - const requests = await requestRepository - .createQueryBuilder('request') - .leftJoinAndSelect('request.media', 'media') - .where('(media.id = :id)', { - id: media.id, - }) - .andWhere( - `(request.is4k = :is4k AND media.${ - is4k ? 'status4k' : 'status' - } IN (:...mediaStatus))`, - { - mediaStatus: [ - MediaStatus.AVAILABLE, - MediaStatus.PARTIALLY_AVAILABLE, - ], - is4k: is4k, - } - ) - .getMany(); - - // Check if a season is processing or pending to - // make sure we set the media to the correct status - let mediaStatus = MediaStatus.UNKNOWN; + // If media type is tv, check if a season is processing + //to see if we need to keep the external metadata + let isMediaProcessing = false; if (media.mediaType === 'tv') { - mediaStatus = this.findMediaStatus(requests, is4k); + const requestRepository = getRepository(MediaRequest); + + const request = await requestRepository + .createQueryBuilder('request') + .leftJoinAndSelect('request.media', 'media') + .where('(media.id = :id)', { + id: media.id, + }) + .andWhere( + '(request.is4k = :is4k AND request.status = :requestStatus)', + { + requestStatus: MediaRequestStatus.APPROVED, + is4k: is4k, + } + ) + .getOne(); + + if (request) { + isMediaProcessing = true; + } } - media[is4k ? 'status4k' : 'status'] = mediaStatus; - media[is4k ? 'serviceId4k' : 'serviceId'] = - mediaStatus === MediaStatus.PROCESSING - ? media[is4k ? 'serviceId4k' : 'serviceId'] - : null; + // Set the non-4K or 4K media to deleted + // and change related columns to null if media + // is not processing + media[is4k ? 'status4k' : 'status'] = MediaStatus.DELETED; + media[is4k ? 'serviceId4k' : 'serviceId'] = isMediaProcessing + ? media[is4k ? 'serviceId4k' : 'serviceId'] + : null; media[is4k ? 'externalServiceId4k' : 'externalServiceId'] = - mediaStatus === MediaStatus.PROCESSING + isMediaProcessing ? media[is4k ? 'externalServiceId4k' : 'externalServiceId'] : null; media[is4k ? 'externalServiceSlug4k' : 'externalServiceSlug'] = - mediaStatus === MediaStatus.PROCESSING + isMediaProcessing ? media[is4k ? 'externalServiceSlug4k' : 'externalServiceSlug'] : null; - media[is4k ? 'ratingKey4k' : 'ratingKey'] = - mediaStatus === MediaStatus.PROCESSING - ? media[is4k ? 'ratingKey4k' : 'ratingKey'] - : null; + media[is4k ? 'ratingKey4k' : 'ratingKey'] = isMediaProcessing + ? media[is4k ? 'ratingKey4k' : 'ratingKey'] + : null; logger.info( `The ${is4k ? '4K' : 'non-4K'} ${ @@ -338,13 +302,6 @@ class AvailabilitySync { ); await mediaRepository.save({ media, ...media }); - - // Only delete media request if type is movie. - // Type tv request deletion is handled - // in the season request entity - if (requests.length > 0 && media.mediaType === 'movie') { - await requestRepository.remove(requests); - } } catch (ex) { logger.debug( `Failure updating the ${is4k ? '4K' : 'non-4K'} ${ @@ -364,35 +321,21 @@ class AvailabilitySync { is4k: boolean ): Promise { const mediaRepository = getRepository(Media); - const seasonRequestRepository = getRepository(SeasonRequest); + // Filter out only the values that are false + // (media that should be deleted) const seasonsPendingRemoval = new Map( // Disabled linter as only the value is needed from the filter // eslint-disable-next-line @typescript-eslint/no-unused-vars [...seasons].filter(([_, exists]) => !exists) ); + // Retrieve the season keys to pass into our log const seasonKeys = [...seasonsPendingRemoval.keys()]; try { - // Need to check and see if there are any related season - // requests. If they are, we will need to delete them. - const seasonRequests = await seasonRequestRepository - .createQueryBuilder('seasonRequest') - .leftJoinAndSelect('seasonRequest.request', 'request') - .leftJoinAndSelect('request.media', 'media') - .where('(media.id = :id)', { id: media.id }) - .andWhere( - '(request.is4k = :is4k AND seasonRequest.seasonNumber IN (:...seasonNumbers))', - { - seasonNumbers: seasonKeys, - is4k: is4k, - } - ) - .getMany(); - for (const mediaSeason of media.seasons) { if (seasonsPendingRemoval.has(mediaSeason.seasonNumber)) { - mediaSeason[is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN; + mediaSeason[is4k ? 'status4k' : 'status'] = MediaStatus.DELETED; } } @@ -414,16 +357,12 @@ class AvailabilitySync { await mediaRepository.save({ media, ...media }); - if (seasonRequests.length > 0) { - await seasonRequestRepository.remove(seasonRequests); - } - logger.info( `The ${is4k ? '4K' : 'non-4K'} season(s) [${seasonKeys}] [TMDB ID ${ media.tmdbId }] was not found in any ${ media.mediaType === 'tv' ? 'Sonarr' : 'Radarr' - } and Plex instance. Status will be changed to unknown.`, + } and Plex instance. Status will be changed to deleted.`, { label: 'AvailabilitySync' } ); } catch (ex) { @@ -448,53 +387,47 @@ class AvailabilitySync { // Check for availability in all of the available radarr servers // If any find the media, we will assume the media exists for (const server of this.radarrServers) { - const radarrAPI = new RadarrAPI({ - apiKey: server.apiKey, - url: RadarrAPI.buildUrl(server, '/api/v3'), - }); + if (server.is4k === is4k) { + const radarrAPI = new RadarrAPI({ + apiKey: server.apiKey, + url: RadarrAPI.buildUrl(server, '/api/v3'), + }); - try { - let radarr: RadarrMovie | undefined; + try { + let radarr: RadarrMovie | undefined; - if (!server.is4k && media.externalServiceId && !is4k) { - radarr = await radarrAPI.getMovie({ - id: media.externalServiceId, - }); - } + if (media.externalServiceId && !is4k) { + radarr = await radarrAPI.getMovie({ + id: media.externalServiceId, + }); + } - if (server.is4k && media.externalServiceId4k && is4k) { - radarr = await radarrAPI.getMovie({ - id: media.externalServiceId4k, - }); - } + if (media.externalServiceId4k && is4k) { + radarr = await radarrAPI.getMovie({ + id: media.externalServiceId4k, + }); + } - if (radarr && radarr.hasFile) { - existsInRadarr = true; - } - } catch (ex) { - if (!ex.message.includes('404')) { - existsInRadarr = true; - logger.debug( - `Failure retrieving the ${is4k ? '4K' : 'non-4K'} movie [TMDB ID ${ - media.tmdbId - }] from Radarr.`, - { - errorMessage: ex.message, - label: 'AvailabilitySync', - } - ); + if (radarr && radarr.hasFile) { + existsInRadarr = true; + } + } catch (ex) { + if (!ex.message.includes('404')) { + existsInRadarr = true; + logger.debug( + `Failure retrieving the ${ + is4k ? '4K' : 'non-4K' + } movie [TMDB ID ${media.tmdbId}] from Radarr.`, + { + errorMessage: ex.message, + label: 'AvailabilitySync', + } + ); + } } } } - if ((!radarr || !radarr.hasFile) && !existsInPlex) { - movieExists = false; - } - - if ((!radarr4k || !radarr4k.hasFile) && !existsInPlex4k) { - movieExists4k = false; - } - return existsInRadarr; } @@ -508,65 +441,49 @@ class AvailabilitySync { // Check for availability in all of the available sonarr servers // If any find the media, we will assume the media exists for (const server of this.sonarrServers) { - const sonarrAPI = new SonarrAPI({ - apiKey: server.apiKey, - url: SonarrAPI.buildUrl(server, '/api/v3'), - }); + if (server.is4k === is4k) { + const sonarrAPI = new SonarrAPI({ + apiKey: server.apiKey, + url: SonarrAPI.buildUrl(server, '/api/v3'), + }); - try { - let sonarr: SonarrSeries | undefined; + try { + let sonarr: SonarrSeries | undefined; - if (!server.is4k && media.externalServiceId && !is4k) { - sonarr = await sonarrAPI.getSeriesById(media.externalServiceId); - this.sonarrSeasonsCache[`${server.id}-${media.externalServiceId}`] = - sonarr.seasons; - } + if (media.externalServiceId && !is4k) { + sonarr = await sonarrAPI.getSeriesById(media.externalServiceId); + this.sonarrSeasonsCache[`${server.id}-${media.externalServiceId}`] = + sonarr.seasons; + } - if (server.is4k && media.externalServiceId4k && is4k) { - sonarr = await sonarrAPI.getSeriesById(media.externalServiceId4k); - this.sonarrSeasonsCache[`${server.id}-${media.externalServiceId4k}`] = - sonarr.seasons; - } + if (media.externalServiceId4k && is4k) { + sonarr = await sonarrAPI.getSeriesById(media.externalServiceId4k); + this.sonarrSeasonsCache[ + `${server.id}-${media.externalServiceId4k}` + ] = sonarr.seasons; + } - if (sonarr && sonarr.statistics.episodeFileCount > 0) { - existsInSonarr = true; - } - } catch (ex) { - if (!ex.message.includes('404')) { - existsInSonarr = true; - preventSeasonSearch = true; - logger.debug( - `Failure retrieving the ${is4k ? '4K' : 'non-4K'} show [TMDB ID ${ - media.tmdbId - }] from Sonarr.`, - { - errorMessage: ex.message, - label: 'AvailabilitySync', - } - ); + if (sonarr && sonarr.statistics.episodeFileCount > 0) { + existsInSonarr = true; + } + } catch (ex) { + if (!ex.message.includes('404')) { + existsInSonarr = true; + preventSeasonSearch = true; + logger.debug( + `Failure retrieving the ${is4k ? '4K' : 'non-4K'} show [TMDB ID ${ + media.tmdbId + }] from Sonarr.`, + { + errorMessage: ex.message, + label: 'AvailabilitySync', + } + ); + } } } } - if ( - (!sonarr || sonarr.statistics.episodeFileCount === 0) && - !existsInPlex - ) { - showExists = false; - } - - if ( - (!sonarr4k || sonarr4k.statistics.episodeFileCount === 0) && - !existsInPlex4k - ) { - showExists4k = false; - } - - // Here we check each season for availability - for (const season of media.seasons) { - await this.seasonExists(media, season, showExists, showExists4k); - } - // Here we check each season for availability // If the API returns an error other than a 404, // we will have to prevent the season check from happening