From cf1d556b29a777d0351111a2c9f853f2b6863c00 Mon Sep 17 00:00:00 2001 From: Brandon Cohen Date: Thu, 22 Jun 2023 01:34:32 -0400 Subject: [PATCH] fix: handle issue causing incorrect media to change back to unknown --- server/lib/availabilitySync.ts | 766 +++++++++++++++++---------------- 1 file changed, 387 insertions(+), 379 deletions(-) diff --git a/server/lib/availabilitySync.ts b/server/lib/availabilitySync.ts index 231dd9a20a..182f0f1686 100644 --- a/server/lib/availabilitySync.ts +++ b/server/lib/availabilitySync.ts @@ -1,10 +1,10 @@ -import type { PlexMetadata } from '@server/api/plexapi'; +import type { PlexLibraryItem, PlexMetadata } from '@server/api/plexapi'; import PlexAPI from '@server/api/plexapi'; import type { RadarrMovie } from '@server/api/servarr/radarr'; import RadarrAPI from '@server/api/servarr/radarr'; import type { SonarrSeason, SonarrSeries } from '@server/api/servarr/sonarr'; import SonarrAPI from '@server/api/servarr/sonarr'; -import { MediaStatus } from '@server/constants/media'; +import { MediaRequestStatus, MediaStatus } from '@server/constants/media'; import { getRepository } from '@server/datasource'; import Media from '@server/entity/Media'; import MediaRequest from '@server/entity/MediaRequest'; @@ -43,9 +43,6 @@ class AvailabilitySync { }); const mediaRepository = getRepository(Media); const requestRepository = getRepository(MediaRequest); - const seasonRepository = getRepository(Season); - const seasonRequestRepository = getRepository(SeasonRequest); - const pageSize = 50; for await (const media of this.loadAvailableMediaPaginated(pageSize)) { @@ -57,141 +54,92 @@ class AvailabilitySync { // We can not delete media so if both versions do not exist, we will change both columns to unknown or null if (!mediaExists) { + logger.info( + `Media ID ${media.id} does not exist in any of your media instances. Status will be changed to unknown.`, + { label: 'AvailabilitySync' } + ); + + // Find all related requests only if + // related media is available + const requests = await requestRepository + .createQueryBuilder('request') + .leftJoinAndSelect('request.media', 'media') + .where('(media.id = :id AND media.mediaType = :mediaType)', { + id: media.id, + mediaType: 'movie', + }) + .andWhere( + '((request.is4k = 0 AND media.status = :mediaStatus) OR (request.is4k = 1 AND media.status4k = :mediaStatus))', + { + mediaStatus: MediaStatus.AVAILABLE, + } + ) + .getMany(); + + let mediaStatus = MediaStatus.UNKNOWN; + let mediaStatus4k = MediaStatus.UNKNOWN; + + if (media.mediaType === 'tv') { + mediaStatus = await this.findMediaStatus(media, false); + mediaStatus4k = await this.findMediaStatus(media, true); + } + if ( - media.status !== MediaStatus.UNKNOWN || - media.status4k !== MediaStatus.UNKNOWN + media.status === MediaStatus.AVAILABLE || + media.status === MediaStatus.PARTIALLY_AVAILABLE ) { - const request = await requestRepository.find({ - relations: { - media: true, - }, - where: { media: { id: media.id } }, - }); - - logger.info( - `Media ID ${media.id} does not exist in any of your media instances. Status will be changed to unknown.`, - { label: 'AvailabilitySync' } - ); - - await mediaRepository.update(media.id, { - status: MediaStatus.UNKNOWN, - status4k: MediaStatus.UNKNOWN, - serviceId: null, - serviceId4k: null, - externalServiceId: null, - externalServiceId4k: null, - externalServiceSlug: null, - externalServiceSlug4k: null, - ratingKey: null, - ratingKey4k: null, - }); - - await requestRepository.remove(request); + (media.status = mediaStatus), + (media.serviceId = + mediaStatus === MediaStatus.PROCESSING + ? media.serviceId + : null), + (media.externalServiceId = + mediaStatus === MediaStatus.PROCESSING + ? media.externalServiceId + : null), + (media.externalServiceSlug = + mediaStatus === MediaStatus.PROCESSING + ? media.externalServiceSlug + : null), + (media.ratingKey = + mediaStatus === MediaStatus.PROCESSING + ? media.ratingKey + : null); } - } - if (media.mediaType === 'tv') { - // ok, the show itself exists, but do all it's seasons? - const seasons = await seasonRepository.find({ - where: [ - { status: MediaStatus.AVAILABLE, media: { id: media.id } }, - { - status: MediaStatus.PARTIALLY_AVAILABLE, - media: { id: media.id }, - }, - { status4k: MediaStatus.AVAILABLE, media: { id: media.id } }, - { - status4k: MediaStatus.PARTIALLY_AVAILABLE, - media: { id: media.id }, - }, - ], - }); + if ( + media.status4k === MediaStatus.AVAILABLE || + media.status4k === MediaStatus.PARTIALLY_AVAILABLE + ) { + (media.status4k = mediaStatus4k), + (media.serviceId4k = + mediaStatus === MediaStatus.PROCESSING + ? media.serviceId4k + : null), + (media.externalServiceId4k = + mediaStatus === MediaStatus.PROCESSING + ? media.externalServiceId4k + : null), + (media.externalServiceSlug4k = + mediaStatus === MediaStatus.PROCESSING + ? media.externalServiceSlug4k + : null), + (media.ratingKey4k = + mediaStatus === MediaStatus.PROCESSING + ? media.ratingKey4k + : null); + } - let didDeleteSeasons = false; - for (const season of seasons) { - if ( - !mediaExists && - (season.status !== MediaStatus.UNKNOWN || - season.status4k !== MediaStatus.UNKNOWN) - ) { - await seasonRepository.update( - { id: season.id }, - { - status: MediaStatus.UNKNOWN, - status4k: MediaStatus.UNKNOWN, - } - ); - } else { - const seasonExists = await this.seasonExists(media, season); - - if (!seasonExists) { - logger.info( - `Removing season ${season.seasonNumber}, media ID ${media.id} because it does not exist in any of your media instances.`, - { label: 'AvailabilitySync' } - ); - - if ( - season.status !== MediaStatus.UNKNOWN || - season.status4k !== MediaStatus.UNKNOWN - ) { - await seasonRepository.update( - { id: season.id }, - { - status: MediaStatus.UNKNOWN, - status4k: MediaStatus.UNKNOWN, - } - ); - } - - const seasonToBeDeleted = await seasonRequestRepository.findOne( - { - relations: { - request: { - media: true, - }, - }, - where: { - request: { - media: { - id: media.id, - }, - }, - seasonNumber: season.seasonNumber, - }, - } - ); - - if (seasonToBeDeleted) { - await seasonRequestRepository.remove(seasonToBeDeleted); - } - - didDeleteSeasons = true; - } - } + await mediaRepository.save({ media, ...media }); - if (didDeleteSeasons) { - if ( - media.status === MediaStatus.AVAILABLE || - media.status4k === MediaStatus.AVAILABLE - ) { - logger.info( - `Marking media ID ${media.id} as PARTIALLY_AVAILABLE because season removal has occurred.`, - { label: 'AvailabilitySync' } - ); - - if (media.status === MediaStatus.AVAILABLE) { - await mediaRepository.update(media.id, { - status: MediaStatus.PARTIALLY_AVAILABLE, - }); - } - - if (media.status4k === MediaStatus.AVAILABLE) { - await mediaRepository.update(media.id, { - status4k: MediaStatus.PARTIALLY_AVAILABLE, - }); - } - } - } + if (requests.length > 0) { + await requestRepository.remove(requests); + } + } + + if (media.mediaType === 'tv') { + for (const season of media.seasons) { + this.seasonExists(media, season); } } } @@ -212,6 +160,46 @@ class AvailabilitySync { this.running = false; } + private async findMediaStatus( + media: Media, + is4k?: boolean + ): Promise { + const requestRepository = getRepository(MediaRequest); + + const requests = await requestRepository + .createQueryBuilder('request') + .leftJoinAndSelect('request.media', 'media') + .where('(media.id = :id)', { + id: media.id, + }) + .andWhere('(request.status IN (:...requestStatus))', { + requestStatus: [ + MediaRequestStatus.APPROVED, + MediaRequestStatus.PENDING, + ], + }) + .andWhere('request.is4k = :is4k ', { + is4k: is4k, + }) + .getMany(); + + let mediaStatus: MediaStatus; + + if ( + requests.some((request) => request.status === MediaRequestStatus.APPROVED) + ) { + mediaStatus = MediaStatus.PROCESSING; + } else if ( + requests.some((request) => request.status === MediaRequestStatus.PENDING) + ) { + mediaStatus = MediaStatus.PENDING; + } else { + mediaStatus = MediaStatus.UNKNOWN; + } + + return mediaStatus; + } + private async *loadAvailableMediaPaginated(pageSize: number) { let offset = 0; const mediaRepository = getRepository(Media); @@ -238,56 +226,74 @@ class AvailabilitySync { const mediaRepository = getRepository(Media); const requestRepository = getRepository(MediaRequest); - const isTVType = media.mediaType === 'tv'; - try { - const request = await requestRepository.findOne({ - relations: { - media: true, - }, - where: { media: { id: media.id }, is4k: is4k ? true : false }, - }); - logger.info( `Media ID ${media.id} does not exist in your ${ is4k ? '4k' : 'non-4k' } ${ - isTVType ? 'Sonarr' : 'Radarr' + media.mediaType === 'tv' ? 'Sonarr' : 'Radarr' } and Plex instance. Status will be changed to unknown.`, { label: 'AvailabilitySync' } ); + // Check if a season is processing or pending to + // make sure we set media to the correct status + let mediaStatus = MediaStatus.UNKNOWN; + + if (media.mediaType === 'tv') { + mediaStatus = await this.findMediaStatus(media, is4k); + } + await mediaRepository.update( media.id, is4k ? { - status4k: MediaStatus.UNKNOWN, - serviceId4k: null, - externalServiceId4k: null, - externalServiceSlug4k: null, - ratingKey4k: null, + status4k: mediaStatus, + serviceId4k: + mediaStatus === MediaStatus.PROCESSING + ? media.serviceId4k + : null, + externalServiceId4k: + mediaStatus === MediaStatus.PROCESSING + ? media.externalServiceId4k + : null, + externalServiceSlug4k: + mediaStatus === MediaStatus.PROCESSING + ? media.externalServiceSlug4k + : null, + ratingKey4k: + mediaStatus === MediaStatus.PROCESSING + ? media.ratingKey4k + : null, } : { - status: MediaStatus.UNKNOWN, - serviceId: null, - externalServiceId: null, - externalServiceSlug: null, - ratingKey: null, + status: mediaStatus, + serviceId: + mediaStatus === MediaStatus.PROCESSING ? media.serviceId : null, + externalServiceId: + mediaStatus === MediaStatus.PROCESSING + ? media.externalServiceId + : null, + externalServiceSlug: + mediaStatus === MediaStatus.PROCESSING + ? media.externalServiceSlug + : null, + ratingKey: + mediaStatus === MediaStatus.PROCESSING ? media.ratingKey : null, } ); - if (isTVType) { - const seasonRepository = getRepository(Season); - - await seasonRepository?.update( - { media: { id: media.id } }, - is4k - ? { status4k: MediaStatus.UNKNOWN } - : { status: MediaStatus.UNKNOWN } - ); + if (media.mediaType === 'movie') { + const request = await requestRepository.findOne({ + relations: { + media: true, + }, + where: { media: { id: media.id }, is4k: is4k }, + }); + if (request) { + await requestRepository.delete({ id: request.id }); + } } - - await requestRepository.delete({ id: request?.id }); } catch (ex) { logger.debug(`Failure updating media ID ${media.id}`, { errorMessage: ex.message, @@ -301,8 +307,10 @@ class AvailabilitySync { existsInPlex: boolean, existsInPlex4k: boolean ): Promise { - let existsInRadarr = true; - let existsInRadarr4k = true; + let movieExists = true; + let movieExists4k = true; + let radarr: RadarrMovie | undefined; + let radarr4k: RadarrMovie | undefined; for (const server of this.radarrServers) { const api = new RadarrAPI({ @@ -312,23 +320,12 @@ class AvailabilitySync { try { // Check if both exist or if a single non-4k or 4k exists // If both do not exist we will return false - - let meta: RadarrMovie | undefined; - if (!server.is4k && media.externalServiceId) { - meta = await api.getMovie({ id: media.externalServiceId }); + radarr = await api.getMovie({ id: media.externalServiceId }); } if (server.is4k && media.externalServiceId4k) { - meta = await api.getMovie({ id: media.externalServiceId4k }); - } - - if (!server.is4k && (!meta || !meta.hasFile)) { - existsInRadarr = false; - } - - if (server.is4k && (!meta || !meta.hasFile)) { - existsInRadarr4k = false; + radarr4k = await api.getMovie({ id: media.externalServiceId4k }); } } catch (ex) { logger.debug( @@ -340,39 +337,32 @@ class AvailabilitySync { label: 'AvailabilitySync', } ); - if (!server.is4k) { - existsInRadarr = false; - } - - if (server.is4k) { - existsInRadarr4k = false; - } } } + if ((!radarr || !radarr.hasFile) && !existsInPlex) { + movieExists = false; + } + + if ((!radarr4k || !radarr4k.hasFile) && !existsInPlex4k) { + movieExists4k = false; + } + // If only a single non-4k or 4k exists, then change entity columns accordingly // Related media request will then be deleted - if ( - !existsInRadarr && - (existsInRadarr4k || existsInPlex4k) && - !existsInPlex - ) { - if (media.status !== MediaStatus.UNKNOWN) { + if (!movieExists && movieExists4k) { + if (media.status === MediaStatus.AVAILABLE) { this.mediaUpdater(media, false); } } - if ( - (existsInRadarr || existsInPlex) && - !existsInRadarr4k && - !existsInPlex4k - ) { - if (media.status4k !== MediaStatus.UNKNOWN) { + if (movieExists && !movieExists4k) { + if (media.status4k === MediaStatus.AVAILABLE) { this.mediaUpdater(media, true); } } - if (existsInRadarr || existsInRadarr4k || existsInPlex || existsInPlex4k) { + if (movieExists || movieExists4k) { return true; } @@ -384,8 +374,10 @@ class AvailabilitySync { existsInPlex: boolean, existsInPlex4k: boolean ): Promise { - let existsInSonarr = true; - let existsInSonarr4k = true; + let showExists = true; + let showExists4k = true; + let sonarr: SonarrSeries | undefined; + let sonarr4k: SonarrSeries | undefined; for (const server of this.sonarrServers) { const api = new SonarrAPI({ @@ -395,27 +387,16 @@ class AvailabilitySync { try { // Check if both exist or if a single non-4k or 4k exists // If both do not exist we will return false - - let meta: SonarrSeries | undefined; - if (!server.is4k && media.externalServiceId) { - meta = await api.getSeriesById(media.externalServiceId); + sonarr = await api.getSeriesById(media.externalServiceId); this.sonarrSeasonsCache[`${server.id}-${media.externalServiceId}`] = - meta.seasons; + sonarr.seasons; } if (server.is4k && media.externalServiceId4k) { - meta = await api.getSeriesById(media.externalServiceId4k); + sonarr4k = await api.getSeriesById(media.externalServiceId4k); this.sonarrSeasonsCache[`${server.id}-${media.externalServiceId4k}`] = - meta.seasons; - } - - if (!server.is4k && (!meta || meta.statistics.episodeFileCount === 0)) { - existsInSonarr = false; - } - - if (server.is4k && (!meta || meta.statistics.episodeFileCount === 0)) { - existsInSonarr4k = false; + sonarr4k.seasons; } } catch (ex) { logger.debug( @@ -427,40 +408,44 @@ class AvailabilitySync { label: 'AvailabilitySync', } ); - - if (!server.is4k) { - existsInSonarr = false; - } - - if (server.is4k) { - existsInSonarr4k = false; - } } } - // If only a single non-4k or 4k exists, then change entity columns accordingly - // Related media request will then be deleted if ( - !existsInSonarr && - (existsInSonarr4k || existsInPlex4k) && + (!sonarr || sonarr.statistics.episodeFileCount === 0) && !existsInPlex ) { - if (media.status !== MediaStatus.UNKNOWN) { - this.mediaUpdater(media, false); - } + showExists = false; } if ( - (existsInSonarr || existsInPlex) && - !existsInSonarr4k && + (!sonarr4k || sonarr4k.statistics.episodeFileCount === 0) && !existsInPlex4k ) { - if (media.status4k !== MediaStatus.UNKNOWN) { + showExists4k = false; + } + + // If only a single non-4k or 4k exists, then change entity columns accordingly + // Related media request will then be deleted + if (!showExists && showExists4k) { + if ( + media.status === MediaStatus.AVAILABLE || + media.status === MediaStatus.PARTIALLY_AVAILABLE + ) { + this.mediaUpdater(media, false); + } + } + + if (showExists && !showExists4k) { + if ( + media.status4k === MediaStatus.AVAILABLE || + media.status4k === MediaStatus.PARTIALLY_AVAILABLE + ) { this.mediaUpdater(media, true); } } - if (existsInSonarr || existsInSonarr4k || existsInPlex || existsInPlex4k) { + if (showExists || showExists4k) { return true; } @@ -472,9 +457,11 @@ class AvailabilitySync { season: Season, seasonExistsInPlex: boolean, seasonExistsInPlex4k: boolean - ): Promise { - let seasonExistsInSonarr = true; - let seasonExistsInSonarr4k = true; + ): Promise { + let seasonExists = true; + let seasonExists4k = true; + let sonarrSeasons: SonarrSeason[] | undefined; + let sonarrSeasons4k: SonarrSeason[] | undefined; const mediaRepository = getRepository(Media); const seasonRepository = getRepository(Season); @@ -489,44 +476,22 @@ class AvailabilitySync { try { // Here we can use the cache we built when we fetched the series with mediaExistsInSonarr // If the cache does not have data, we will fetch with the api route - - let seasons: SonarrSeason[] = - this.sonarrSeasonsCache[ - `${server.id}-${ - !server.is4k ? media.externalServiceId : media.externalServiceId4k - }` - ]; - if (!server.is4k && media.externalServiceId) { - seasons = + sonarrSeasons = this.sonarrSeasonsCache[ `${server.id}-${media.externalServiceId}` ] ?? (await api.getSeriesById(media.externalServiceId)).seasons; this.sonarrSeasonsCache[`${server.id}-${media.externalServiceId}`] = - seasons; + sonarrSeasons; } if (server.is4k && media.externalServiceId4k) { - seasons = + sonarrSeasons4k = this.sonarrSeasonsCache[ `${server.id}-${media.externalServiceId4k}` ] ?? (await api.getSeriesById(media.externalServiceId4k)).seasons; this.sonarrSeasonsCache[`${server.id}-${media.externalServiceId4k}`] = - seasons; - } - - const seasonIsUnavailable = seasons?.find( - ({ seasonNumber, statistics }) => - season.seasonNumber === seasonNumber && - statistics?.episodeFileCount === 0 - ); - - if (!server.is4k && seasonIsUnavailable) { - seasonExistsInSonarr = false; - } - - if (server.is4k && seasonIsUnavailable) { - seasonExistsInSonarr4k = false; + sonarrSeasons4k; } } catch (ex) { logger.debug( @@ -538,27 +503,40 @@ class AvailabilitySync { label: 'AvailabilitySync', } ); + } + } - if (!server.is4k) { - seasonExistsInSonarr = false; - } + const seasonIsUnavailable = sonarrSeasons?.find( + ({ seasonNumber, statistics }) => + season.seasonNumber === seasonNumber && + statistics?.episodeFileCount === 0 + ); - if (server.is4k) { - seasonExistsInSonarr4k = false; - } - } + const seasonIsUnavailable4k = sonarrSeasons4k?.find( + ({ seasonNumber, statistics }) => + season.seasonNumber === seasonNumber && + statistics?.episodeFileCount === 0 + ); + + if ((seasonIsUnavailable || !sonarrSeasons) && !seasonExistsInPlex) { + seasonExists = false; + } + + if ((seasonIsUnavailable4k || !sonarrSeasons4k) && !seasonExistsInPlex4k) { + seasonExists4k = false; } try { - const seasonToBeDeleted = await seasonRequestRepository.findOne({ + const seasonRequests = await seasonRequestRepository.find({ relations: { request: { - media: true, + media: { + seasons: true, + }, }, }, where: { request: { - is4k: seasonExistsInSonarr ? true : false, media: { id: media.id, }, @@ -567,116 +545,150 @@ class AvailabilitySync { }, }); + const filteredSeasonRequests = seasonRequests.filter( + (seasonRequest) => + (!seasonRequest.request.is4k && + !seasonExists && + (season.status === MediaStatus.AVAILABLE || + season.status === MediaStatus.PARTIALLY_AVAILABLE)) || + (seasonRequest.request.is4k && + !seasonExists4k && + (season.status4k === MediaStatus.AVAILABLE || + season.status4k === MediaStatus.PARTIALLY_AVAILABLE)) + ); + + let deletedSeason = false; + // If season does not exist, we will change status to unknown and delete related season request // If parent media request is empty(all related seasons have been removed), parent is automatically deleted - if ( - !seasonExistsInSonarr && - (seasonExistsInSonarr4k || seasonExistsInPlex4k) && - !seasonExistsInPlex - ) { - if (season.status !== MediaStatus.UNKNOWN) { + if (!seasonExists && seasonExists4k) { + if ( + season.status === MediaStatus.AVAILABLE || + season.status === MediaStatus.PARTIALLY_AVAILABLE + ) { logger.info( `Season ${season.seasonNumber}, media ID ${media.id} does not exist in your non-4k Sonarr and Plex instance. Status will be changed to unknown.`, { label: 'AvailabilitySync' } ); - await seasonRepository.update(season.id, { - status: MediaStatus.UNKNOWN, - }); - if (seasonToBeDeleted) { - await seasonRequestRepository.remove(seasonToBeDeleted); - } - - if (media.status === MediaStatus.AVAILABLE) { - logger.info( - `Marking media ID ${media.id} as PARTIALLY_AVAILABLE because season removal has occurred.`, - { label: 'AvailabilitySync' } - ); - await mediaRepository.update(media.id, { - status: MediaStatus.PARTIALLY_AVAILABLE, - }); - } + season.status = MediaStatus.UNKNOWN; + deletedSeason = true; } } - if ( - (seasonExistsInSonarr || seasonExistsInPlex) && - !seasonExistsInSonarr4k && - !seasonExistsInPlex4k - ) { - if (season.status4k !== MediaStatus.UNKNOWN) { + if (seasonExists && !seasonExists4k) { + if ( + season.status4k === MediaStatus.AVAILABLE || + season.status4k === MediaStatus.PARTIALLY_AVAILABLE + ) { logger.info( `Season ${season.seasonNumber}, media ID ${media.id} does not exist in your 4k Sonarr and Plex instance. Status will be changed to unknown.`, { label: 'AvailabilitySync' } ); - await seasonRepository.update(season.id, { - status4k: MediaStatus.UNKNOWN, - }); - if (seasonToBeDeleted) { - await seasonRequestRepository.remove(seasonToBeDeleted); - } + season.status4k = MediaStatus.UNKNOWN; + deletedSeason = true; + } + } - if (media.status4k === MediaStatus.AVAILABLE) { - logger.info( - `Marking media ID ${media.id} as PARTIALLY_AVAILABLE because season removal has occurred.`, - { label: 'AvailabilitySync' } - ); - await mediaRepository.update(media.id, { - status4k: MediaStatus.PARTIALLY_AVAILABLE, - }); - } + if (!seasonExists && !seasonExists4k) { + if ( + season.status === MediaStatus.AVAILABLE || + season.status === MediaStatus.PARTIALLY_AVAILABLE + ) { + season.status = MediaStatus.UNKNOWN; + deletedSeason = true; + } + if ( + season.status4k === MediaStatus.AVAILABLE || + season.status4k === MediaStatus.PARTIALLY_AVAILABLE + ) { + season.status4k = MediaStatus.UNKNOWN; + deletedSeason = true; + } + } + + if (deletedSeason) { + await seasonRepository.save(season); + + if (!seasonExists && !seasonExists4k) { + logger.info( + `Removing season ${season.seasonNumber}, media ID ${media.id} because it does not exist in any of your media instances.`, + { label: 'AvailabilitySync' } + ); + } + + if (media.status === MediaStatus.AVAILABLE) { + logger.info( + `Marking media ID ${media.id} as PARTIALLY_AVAILABLE because season removal has occurred.`, + { label: 'AvailabilitySync' } + ); + await mediaRepository.update(media.id, { + status: MediaStatus.PARTIALLY_AVAILABLE, + }); + } + + if (media.status4k === MediaStatus.AVAILABLE) { + logger.info( + `Marking media ID ${media.id} as PARTIALLY_AVAILABLE because season removal has occurred.`, + { label: 'AvailabilitySync' } + ); + await mediaRepository.update(media.id, { + status4k: MediaStatus.PARTIALLY_AVAILABLE, + }); } } + + if (filteredSeasonRequests.length > 0) { + await seasonRequestRepository.remove(filteredSeasonRequests); + } } catch (ex) { logger.debug(`Failure updating media ID ${media.id}`, { errorMessage: ex.message, label: 'AvailabilitySync', }); } - - if ( - seasonExistsInSonarr || - seasonExistsInSonarr4k || - seasonExistsInPlex || - seasonExistsInPlex4k - ) { - return true; - } - - return false; } private async mediaExists(media: Media): Promise { const ratingKey = media.ratingKey; const ratingKey4k = media.ratingKey4k; - let existsInPlex = false; - let existsInPlex4k = false; + let existsInPlex = true; + let existsInPlex4k = true; + let plex: PlexLibraryItem | undefined; + let plex4k: PlexLibraryItem | undefined; // Check each plex instance to see if media exists - try { - if (ratingKey) { - const meta = await this.plexClient?.getMetadata(ratingKey); - if (meta) { - existsInPlex = true; - } + if (ratingKey) { + try { + plex = await this.plexClient?.getMetadata(ratingKey); + } catch (ex) { + logger.debug(`Failed to retrieve Plex non-4k metadata.`, { + errorMessage: ex.message, + label: 'AvailabilitySync', + }); } + } - if (ratingKey4k) { - const meta4k = await this.plexClient?.getMetadata(ratingKey4k); - if (meta4k) { - existsInPlex4k = true; - } - } - } catch (ex) { - if (!ex.message.includes('response code: 404')) { - logger.debug(`Failed to retrieve plex metadata`, { + if (ratingKey4k) { + try { + plex4k = await this.plexClient?.getMetadata(ratingKey4k); + } catch (ex) { + logger.debug(`Failed to retrieve Plex 4k metadata.`, { errorMessage: ex.message, label: 'AvailabilitySync', }); } } + + if (!plex) { + existsInPlex = false; + } + + if (!plex4k) { + existsInPlex4k = false; + } // Base case if both media versions exist in plex if (existsInPlex && existsInPlex4k) { return true; @@ -731,70 +743,66 @@ class AvailabilitySync { const ratingKey = media.ratingKey; const ratingKey4k = media.ratingKey4k; - let seasonExistsInPlex = false; - let seasonExistsInPlex4k = false; + let seasonExistsInPlex = true; + let seasonExistsInPlex4k = true; + let plexSeason: PlexMetadata | undefined; + let plexSeason4k: PlexMetadata | undefined; - try { - if (ratingKey) { + // Check each plex instance to see if the season exists + if (ratingKey) { + try { const children = this.plexSeasonsCache[ratingKey] ?? (await this.plexClient?.getChildrenMetadata(ratingKey)) ?? []; this.plexSeasonsCache[ratingKey] = children; - const seasonMeta = children?.find( + plexSeason = children?.find( (child) => child.index === season.seasonNumber ); - - if (seasonMeta) { - seasonExistsInPlex = true; - } + } catch (ex) { + logger.debug(`Failed to retrieve Plex's non-4k season metadata.`, { + errorMessage: ex.message, + label: 'AvailabilitySync', + }); } - if (ratingKey4k) { + } + if (ratingKey4k) { + try { const children4k = this.plexSeasonsCache[ratingKey4k] ?? (await this.plexClient?.getChildrenMetadata(ratingKey4k)) ?? []; this.plexSeasonsCache[ratingKey4k] = children4k; - const seasonMeta4k = children4k?.find( + plexSeason4k = children4k?.find( (child) => child.index === season.seasonNumber ); - - if (seasonMeta4k) { - seasonExistsInPlex4k = true; - } - } - } catch (ex) { - if (!ex.message.includes('response code: 404')) { - logger.debug(`Failed to retrieve plex's children metadata`, { + } catch (ex) { + logger.debug(`Failed to retrieve Plex's 4k season metadata.`, { errorMessage: ex.message, label: 'AvailabilitySync', }); } } + + if (!plexSeason) { + seasonExistsInPlex = false; + } + + if (!plexSeason4k) { + seasonExistsInPlex4k = false; + } + // Base case if both season versions exist in plex if (seasonExistsInPlex && seasonExistsInPlex4k) { return true; } - const existsInSonarr = await this.seasonExistsInSonarr( + await this.seasonExistsInSonarr( media, season, seasonExistsInPlex, seasonExistsInPlex4k ); - - if (existsInSonarr) { - logger.warn( - `Season ${season.seasonNumber}, media ID ${media.id} exists in at least one Sonarr or Plex instance. Media will be updated if set to available.`, - { - label: 'AvailabilitySync', - } - ); - - return true; - } - - return false; } private async initPlexClient() {