From 1cb31f43eafc8840a93e8ca0a0ef155458886be6 Mon Sep 17 00:00:00 2001 From: Brandon Date: Mon, 6 Mar 2023 23:14:22 -0500 Subject: [PATCH 01/13] fix: modified media status handling when media has been deleted --- server/constants/media.ts | 2 ++ server/entity/SeasonRequest.ts | 16 ++++++++++++---- server/lib/availabilitySync.ts | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/server/constants/media.ts b/server/constants/media.ts index de2bf834..cf2e046c 100644 --- a/server/constants/media.ts +++ b/server/constants/media.ts @@ -3,6 +3,7 @@ export enum MediaRequestStatus { APPROVED, DECLINED, FAILED, + COMPLETED, } export enum MediaType { @@ -16,4 +17,5 @@ export enum MediaStatus { PROCESSING, PARTIALLY_AVAILABLE, AVAILABLE, + DELETED, } diff --git a/server/entity/SeasonRequest.ts b/server/entity/SeasonRequest.ts index c55906eb..984c3d3b 100644 --- a/server/entity/SeasonRequest.ts +++ b/server/entity/SeasonRequest.ts @@ -1,7 +1,7 @@ import { MediaRequestStatus } from '@server/constants/media'; import { getRepository } from '@server/datasource'; import { - AfterRemove, + AfterUpdate, Column, CreateDateColumn, Entity, @@ -37,15 +37,23 @@ class SeasonRequest { Object.assign(this, init); } - @AfterRemove() + @AfterUpdate() public async handleRemoveParent(): Promise { const mediaRequestRepository = getRepository(MediaRequest); const requestToBeDeleted = await mediaRequestRepository.findOneOrFail({ where: { id: this.request.id }, }); - if (requestToBeDeleted.seasons.length === 0) { - await mediaRequestRepository.delete({ id: this.request.id }); + const allSeasonsAreCompleted = requestToBeDeleted.seasons.filter( + (season) => { + return season.status === MediaRequestStatus.COMPLETED; + } + ); + + if (requestToBeDeleted.seasons.length === allSeasonsAreCompleted.length) { + await mediaRequestRepository.update(this.request.id, { + status: MediaRequestStatus.COMPLETED, + }); } } } diff --git a/server/lib/availabilitySync.ts b/server/lib/availabilitySync.ts index 0a16302c..398701bc 100644 --- a/server/lib/availabilitySync.ts +++ b/server/lib/availabilitySync.ts @@ -330,7 +330,7 @@ class AvailabilitySync { media.mediaType === 'movie' ? 'movie' : 'show' } [TMDB ID ${media.tmdbId}] was not found in any ${ media.mediaType === 'movie' ? 'Radarr' : 'Sonarr' - } and Plex instance. Status will be changed to unknown.`, + } and Plex instance. Status will be changed to deleted.`, { label: 'AvailabilitySync' } ); From 05ea56188b2f8346e555e9d2667a6059b93a75dd Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 10 Mar 2023 19:49:11 -0500 Subject: [PATCH 02/13] fix: requests will now be updated to completed on scan --- server/lib/scanners/baseScanner.ts | 111 ++++++++++++++++++++++++++++- server/routes/media.ts | 30 +++++++- 2 files changed, 139 insertions(+), 2 deletions(-) diff --git a/server/lib/scanners/baseScanner.ts b/server/lib/scanners/baseScanner.ts index f0f3db7e..170ebee6 100644 --- a/server/lib/scanners/baseScanner.ts +++ b/server/lib/scanners/baseScanner.ts @@ -1,8 +1,14 @@ import TheMovieDb from '@server/api/themoviedb'; -import { MediaStatus, MediaType } from '@server/constants/media'; +import { + MediaRequestStatus, + MediaStatus, + MediaType, +} from '@server/constants/media'; import { getRepository } from '@server/datasource'; import Media from '@server/entity/Media'; +import MediaRequest from '@server/entity/MediaRequest'; import Season from '@server/entity/Season'; +import SeasonRequest from '@server/entity/SeasonRequest'; import { getSettings } from '@server/lib/settings'; import logger from '@server/logger'; import AsyncLock from '@server/utils/asyncLock'; @@ -89,6 +95,53 @@ class BaseScanner { return existing; } + private async requestUpdater(mediaId: number, is4k: boolean) { + const requestRepository = getRepository(MediaRequest); + + const request = await requestRepository.findOne({ + relations: { + media: true, + }, + where: { media: { id: mediaId }, is4k: is4k }, + }); + + await requestRepository.update( + { media: { id: request?.id } }, + { status: MediaRequestStatus.COMPLETED } + ); + } + + private async seasonRequestUpdater( + mediaId: number | undefined, + seasonNumber: number, + is4k: boolean + ) { + const seasonRequestRepository = getRepository(SeasonRequest); + + const seasonToBeCompleted = await seasonRequestRepository.findOne({ + relations: { + request: { + media: true, + }, + }, + where: { + request: { + is4k: is4k, + media: { + id: mediaId, + }, + }, + seasonNumber: seasonNumber, + }, + }); + + if (seasonToBeCompleted) { + await seasonRequestRepository.update(seasonToBeCompleted?.id, { + status: MediaRequestStatus.COMPLETED, + }); + } + } + protected async processMovie( tmdbId: number, { @@ -162,7 +215,16 @@ class BaseScanner { } if (changedExisting) { + if (existing['status'] === MediaStatus.AVAILABLE) { + this.requestUpdater(existing.id, false); + } + + if (existing['status4k'] === MediaStatus.AVAILABLE) { + this.requestUpdater(existing.id, true); + } + await mediaRepository.save(existing); + this.log( `Media for ${title} exists. Changes were detected and the title will be updated.`, 'info' @@ -203,6 +265,15 @@ class BaseScanner { newMedia.ratingKey4k = is4k && this.enable4kMovie ? ratingKey : undefined; } + + if (newMedia['status'] === MediaStatus.AVAILABLE) { + this.requestUpdater(newMedia.id, false); + } + + if (newMedia['status4k'] === MediaStatus.AVAILABLE && existing) { + this.requestUpdater(newMedia.id, true); + } + await mediaRepository.save(newMedia); this.log(`Saved new media: ${title}`); } @@ -297,6 +368,14 @@ class BaseScanner { : season.is4kOverride && season.processing ? MediaStatus.PROCESSING : existingSeason.status4k; + + if (existingSeason['status'] === MediaStatus.AVAILABLE) { + this.seasonRequestUpdater(media?.id, season.seasonNumber, false); + } + + if (existingSeason['status4k'] === MediaStatus.AVAILABLE) { + this.seasonRequestUpdater(media?.id, season.seasonNumber, true); + } } else { newSeasons.push( new Season({ @@ -321,6 +400,18 @@ class BaseScanner { : MediaStatus.UNKNOWN, }) ); + + if (season.totalEpisodes === season.episodes && season.episodes > 0) { + this.seasonRequestUpdater(media?.id, season.seasonNumber, false); + } + + if ( + this.enable4kShow && + season.totalEpisodes === season.episodes4k && + season.episodes4k > 0 + ) { + this.seasonRequestUpdater(media?.id, season.seasonNumber, true); + } } } @@ -439,7 +530,16 @@ class BaseScanner { ) ? MediaStatus.PROCESSING : MediaStatus.UNKNOWN; + + if (isAllStandardSeasons) { + this.requestUpdater(media.id, false); + } + + if (isAll4kSeasons) { + this.requestUpdater(media.id, true); + } await mediaRepository.save(media); + this.log(`Updating existing title: ${title}`); } else { const newMedia = new Media({ @@ -499,7 +599,16 @@ class BaseScanner { ? MediaStatus.PROCESSING : MediaStatus.UNKNOWN, }); + + if (newMedia['status'] === MediaStatus.AVAILABLE) { + this.requestUpdater(newMedia.id, false); + } + + if (newMedia['status4k'] === MediaStatus.AVAILABLE) { + this.requestUpdater(newMedia.id, true); + } await mediaRepository.save(newMedia); + this.log(`Saved ${title}`); } }); diff --git a/server/routes/media.ts b/server/routes/media.ts index 8f93116c..9987380d 100644 --- a/server/routes/media.ts +++ b/server/routes/media.ts @@ -1,7 +1,13 @@ import TautulliAPI from '@server/api/tautulli'; -import { MediaStatus, MediaType } from '@server/constants/media'; +import { + MediaRequestStatus, + MediaStatus, + MediaType, +} from '@server/constants/media'; import { getRepository } from '@server/datasource'; import Media from '@server/entity/Media'; +import MediaRequest from '@server/entity/MediaRequest'; +import SeasonRequest from '@server/entity/SeasonRequest'; import { User } from '@server/entity/User'; import type { MediaResultsResponse, @@ -98,6 +104,8 @@ mediaRoutes.post< isAuthenticated(Permission.MANAGE_REQUESTS), async (req, res, next) => { const mediaRepository = getRepository(Media); + const requestRepository = getRepository(MediaRequest); + const seasonRequestRepository = getRepository(SeasonRequest); const media = await mediaRepository.findOne({ where: { id: Number(req.params.id) }, @@ -138,6 +146,26 @@ mediaRoutes.post< media.status = MediaStatus.UNKNOWN; } + if (req.params.status === 'available') { + const request = await requestRepository.findOne({ + relations: { + media: true, + }, + where: { media: { id: media.id }, is4k: is4k }, + }); + + await requestRepository.update( + { media: { id: request?.id } }, + { status: MediaRequestStatus.COMPLETED } + ); + + request?.seasons.forEach(async (season) => { + await seasonRequestRepository.update(season.id, { + status: MediaRequestStatus.COMPLETED, + }); + }); + } + await mediaRepository.save(media); return res.status(200).json(media); From 16677a4cdf421891c61f72089546b6a1fb118c0f Mon Sep 17 00:00:00 2001 From: Brandon Date: Sat, 18 Mar 2023 19:41:47 -0400 Subject: [PATCH 03/13] fix: modified components to display deleted as a status --- overseerr-api.yml | 16 ++++++++-- server/routes/request.ts | 9 ++++++ .../Common/StatusBadgeMini/index.tsx | 11 ++++++- .../Discover/RecentRequestsSlider/index.tsx | 2 ++ src/components/RequestBlock/index.tsx | 5 +++ src/components/RequestButton/index.tsx | 8 +++-- src/components/RequestList/index.tsx | 8 +++++ .../RequestModal/TvRequestModal.tsx | 4 ++- src/components/StatusBadge/index.tsx | 11 +++++++ src/components/TvDetails/index.tsx | 32 +++++++++++++++++++ src/i18n/globalMessages.ts | 2 ++ 11 files changed, 101 insertions(+), 7 deletions(-) diff --git a/overseerr-api.yml b/overseerr-api.yml index 6e8f5896..4deaa981 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -1053,7 +1053,7 @@ components: status: type: number example: 0 - description: Availability of the media. 1 = `UNKNOWN`, 2 = `PENDING`, 3 = `PROCESSING`, 4 = `PARTIALLY_AVAILABLE`, 5 = `AVAILABLE` + description: Availability of the media. 1 = `UNKNOWN`, 2 = `PENDING`, 3 = `PROCESSING`, 4 = `PARTIALLY_AVAILABLE`, 5 = `AVAILABLE`, 6 = `DELETED` requests: type: array readOnly: true @@ -4955,6 +4955,7 @@ paths: processing, unavailable, failed, + deleted, ] - in: query name: sort @@ -5694,7 +5695,16 @@ paths: schema: type: string nullable: true - enum: [all, available, partial, allavailable, processing, pending] + enum: + [ + all, + available, + partial, + allavailable, + processing, + pending, + deleted, + ] - in: query name: sort schema: @@ -5753,7 +5763,7 @@ paths: example: available schema: type: string - enum: [available, partial, processing, pending, unknown] + enum: [available, partial, processing, pending, unknown, deleted] requestBody: content: application/json: diff --git a/server/routes/request.ts b/server/routes/request.ts index 83c05b48..1f8fcf66 100644 --- a/server/routes/request.ts +++ b/server/routes/request.ts @@ -55,12 +55,17 @@ requestRoutes.get, RequestResultsResponse>( case 'failed': statusFilter = [MediaRequestStatus.FAILED]; break; + case 'completed': + case 'deleted': + statusFilter = [MediaRequestStatus.COMPLETED]; + break; default: statusFilter = [ MediaRequestStatus.PENDING, MediaRequestStatus.APPROVED, MediaRequestStatus.DECLINED, MediaRequestStatus.FAILED, + MediaRequestStatus.COMPLETED, ]; } @@ -79,6 +84,9 @@ requestRoutes.get, RequestResultsResponse>( MediaStatus.PARTIALLY_AVAILABLE, ]; break; + case 'deleted': + mediaStatusFilter = [MediaStatus.DELETED]; + break; default: mediaStatusFilter = [ MediaStatus.UNKNOWN, @@ -86,6 +94,7 @@ requestRoutes.get, RequestResultsResponse>( MediaStatus.PROCESSING, MediaStatus.PARTIALLY_AVAILABLE, MediaStatus.AVAILABLE, + MediaStatus.DELETED, ]; } diff --git a/src/components/Common/StatusBadgeMini/index.tsx b/src/components/Common/StatusBadgeMini/index.tsx index a7e24a37..d98648a2 100644 --- a/src/components/Common/StatusBadgeMini/index.tsx +++ b/src/components/Common/StatusBadgeMini/index.tsx @@ -1,6 +1,11 @@ import Spinner from '@app/assets/spinner.svg'; import { CheckCircleIcon } from '@heroicons/react/20/solid'; -import { BellIcon, ClockIcon, MinusSmallIcon } from '@heroicons/react/24/solid'; +import { + BellIcon, + ClockIcon, + MinusSmallIcon, + TrashIcon, +} from '@heroicons/react/24/solid'; import { MediaStatus } from '@server/constants/media'; interface StatusBadgeMiniProps { @@ -50,6 +55,10 @@ const StatusBadgeMini = ({ ); indicatorIcon = ; break; + case MediaStatus.DELETED: + badgeStyle.push('bg-red-500 border-red-400 ring-red-400 text-red-100'); + indicatorIcon = ; + break; } if (inProgress) { diff --git a/src/components/Discover/RecentRequestsSlider/index.tsx b/src/components/Discover/RecentRequestsSlider/index.tsx index 96309d61..72909c8d 100644 --- a/src/components/Discover/RecentRequestsSlider/index.tsx +++ b/src/components/Discover/RecentRequestsSlider/index.tsx @@ -21,6 +21,8 @@ const RecentRequestsSlider = () => { return null; } + console.log({ requests }); + return ( <>
diff --git a/src/components/RequestBlock/index.tsx b/src/components/RequestBlock/index.tsx index ca8cb5fa..8d3c7499 100644 --- a/src/components/RequestBlock/index.tsx +++ b/src/components/RequestBlock/index.tsx @@ -204,6 +204,11 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => { {intl.formatMessage(globalMessages.failed)} )} + {request.status === MediaRequestStatus.COMPLETED && ( + + {intl.formatMessage(globalMessages.completed)} + + )}
diff --git a/src/components/RequestButton/index.tsx b/src/components/RequestButton/index.tsx index 56e91810..b41a5fde 100644 --- a/src/components/RequestButton/index.tsx +++ b/src/components/RequestButton/index.tsx @@ -264,7 +264,9 @@ const RequestButton = ({ // Standard request button if ( - (!media || media.status === MediaStatus.UNKNOWN) && + (!media || + media.status === MediaStatus.UNKNOWN || + media.status === MediaStatus.DELETED) && hasPermission( [ Permission.REQUEST, @@ -307,7 +309,9 @@ const RequestButton = ({ // 4K request button if ( - (!media || media.status4k === MediaStatus.UNKNOWN) && + (!media || + media.status4k === MediaStatus.UNKNOWN || + media.status4k === MediaStatus.DELETED) && hasPermission( [ Permission.REQUEST_4K, diff --git a/src/components/RequestList/index.tsx b/src/components/RequestList/index.tsx index bffd7c06..b01e36f5 100644 --- a/src/components/RequestList/index.tsx +++ b/src/components/RequestList/index.tsx @@ -34,6 +34,7 @@ enum Filter { AVAILABLE = 'available', UNAVAILABLE = 'unavailable', FAILED = 'failed', + DELETED = 'deleted', } type Sort = 'added' | 'modified'; @@ -99,6 +100,10 @@ const RequestList = () => { ); }, [currentFilter, currentSort, currentPageSize]); + console.log({ currentFilter }); + + console.log({ data }); + if (!data && !error) { return ; } @@ -177,6 +182,9 @@ const RequestList = () => { +
diff --git a/src/components/RequestModal/TvRequestModal.tsx b/src/components/RequestModal/TvRequestModal.tsx index 25c8fd3c..c0e2d4da 100644 --- a/src/components/RequestModal/TvRequestModal.tsx +++ b/src/components/RequestModal/TvRequestModal.tsx @@ -569,7 +569,9 @@ const TvRequestModal = ({ (sn) => sn.seasonNumber === season.seasonNumber && sn[is4k ? 'status4k' : 'status'] !== - MediaStatus.UNKNOWN + MediaStatus.UNKNOWN && + sn[is4k ? 'status4k' : 'status'] !== + MediaStatus.DELETED ); return ( diff --git a/src/components/StatusBadge/index.tsx b/src/components/StatusBadge/index.tsx index b60b7af0..cda8eb59 100644 --- a/src/components/StatusBadge/index.tsx +++ b/src/components/StatusBadge/index.tsx @@ -299,6 +299,17 @@ const StatusBadge = ({ ); + case MediaStatus.DELETED: + return ( + + + {intl.formatMessage(is4k ? messages.status4k : messages.status, { + status: intl.formatMessage(globalMessages.deleted), + })} + + + ); + default: return null; } diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 2f44a7d8..f9ece0fe 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -647,6 +647,18 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
)} + {mSeason?.status === MediaStatus.DELETED && ( + <> +
+ + {intl.formatMessage(globalMessages.deleted)} + +
+
+ +
+ + )} {((!mSeason4k && request4k?.status === MediaRequestStatus.APPROVED) || @@ -733,6 +745,26 @@ const TvDetails = ({ tv }: TvDetailsProps) => { )} + {mSeason4k?.status4k === MediaStatus.DELETED && + show4k && ( + <> +
+ + {intl.formatMessage(messages.status4k, { + status: intl.formatMessage( + globalMessages.deleted + ), + })} + +
+
+ +
+ + )} Date: Mon, 20 Mar 2023 23:35:32 -0400 Subject: [PATCH 04/13] fix: corrected media status switching away from deleted --- server/lib/scanners/baseScanner.ts | 52 +++++++++++++------ server/subscriber/MediaSubscriber.ts | 8 ++- .../Discover/RecentRequestsSlider/index.tsx | 2 - src/components/RequestList/index.tsx | 4 -- .../RequestModal/CollectionRequestModal.tsx | 14 +++-- .../RequestModal/TvRequestModal.tsx | 10 ++-- 6 files changed, 61 insertions(+), 29 deletions(-) diff --git a/server/lib/scanners/baseScanner.ts b/server/lib/scanners/baseScanner.ts index 170ebee6..26f26daf 100644 --- a/server/lib/scanners/baseScanner.ts +++ b/server/lib/scanners/baseScanner.ts @@ -106,7 +106,7 @@ class BaseScanner { }); await requestRepository.update( - { media: { id: request?.id } }, + { id: request?.id }, { status: MediaRequestStatus.COMPLETED } ); } @@ -215,11 +215,11 @@ class BaseScanner { } if (changedExisting) { - if (existing['status'] === MediaStatus.AVAILABLE) { + if (existing.status === MediaStatus.AVAILABLE) { this.requestUpdater(existing.id, false); } - if (existing['status4k'] === MediaStatus.AVAILABLE) { + if (existing.status4k === MediaStatus.AVAILABLE) { this.requestUpdater(existing.id, true); } @@ -352,7 +352,9 @@ class BaseScanner { ? MediaStatus.AVAILABLE : season.episodes > 0 ? MediaStatus.PARTIALLY_AVAILABLE - : !season.is4kOverride && season.processing + : !season.is4kOverride && + season.processing && + existingSeason.status !== MediaStatus.DELETED ? MediaStatus.PROCESSING : existingSeason.status; @@ -365,15 +367,25 @@ class BaseScanner { ? MediaStatus.AVAILABLE : this.enable4kShow && season.episodes4k > 0 ? MediaStatus.PARTIALLY_AVAILABLE - : season.is4kOverride && season.processing + : season.is4kOverride && + season.processing && + existingSeason.status !== MediaStatus.DELETED ? MediaStatus.PROCESSING : existingSeason.status4k; - if (existingSeason['status'] === MediaStatus.AVAILABLE) { + if ( + (season.totalEpisodes === season.episodes && season.episodes > 0) || + existingSeason.status === MediaStatus.AVAILABLE + ) { this.seasonRequestUpdater(media?.id, season.seasonNumber, false); } - if (existingSeason['status4k'] === MediaStatus.AVAILABLE) { + if ( + (this.enable4kShow && + season.episodes4k === season.totalEpisodes && + season.episodes4k > 0) || + existingSeason.status4k === MediaStatus.AVAILABLE + ) { this.seasonRequestUpdater(media?.id, season.seasonNumber, true); } } else { @@ -492,12 +504,18 @@ class BaseScanner { // the status const shouldStayAvailable = media.status === MediaStatus.AVAILABLE && - newSeasons.filter((season) => season.status !== MediaStatus.UNKNOWN) - .length === 0; + newSeasons.filter( + (season) => + season.status !== MediaStatus.UNKNOWN && + season.status !== MediaStatus.DELETED + ).length === 0; const shouldStayAvailable4k = media.status4k === MediaStatus.AVAILABLE && - newSeasons.filter((season) => season.status4k !== MediaStatus.UNKNOWN) - .length === 0; + newSeasons.filter( + (season) => + season.status4k !== MediaStatus.UNKNOWN && + season.status4k !== MediaStatus.DELETED + ).length === 0; media.status = isAllStandardSeasons || shouldStayAvailable @@ -508,11 +526,13 @@ class BaseScanner { season.status === MediaStatus.AVAILABLE ) ? MediaStatus.PARTIALLY_AVAILABLE - : !seasons.length || + : (!seasons.length && media.status !== MediaStatus.DELETED) || media.seasons.some( (season) => season.status === MediaStatus.PROCESSING ) ? MediaStatus.PROCESSING + : media.status === MediaStatus.DELETED + ? MediaStatus.DELETED : MediaStatus.UNKNOWN; media.status4k = (isAll4kSeasons || shouldStayAvailable4k) && this.enable4kShow @@ -524,11 +544,13 @@ class BaseScanner { season.status4k === MediaStatus.AVAILABLE ) ? MediaStatus.PARTIALLY_AVAILABLE - : !seasons.length || + : (!seasons.length && media.status4k !== MediaStatus.DELETED) || media.seasons.some( (season) => season.status4k === MediaStatus.PROCESSING ) ? MediaStatus.PROCESSING + : media.status4k === MediaStatus.DELETED + ? MediaStatus.DELETED : MediaStatus.UNKNOWN; if (isAllStandardSeasons) { @@ -600,11 +622,11 @@ class BaseScanner { : MediaStatus.UNKNOWN, }); - if (newMedia['status'] === MediaStatus.AVAILABLE) { + if (isAllStandardSeasons) { this.requestUpdater(newMedia.id, false); } - if (newMedia['status4k'] === MediaStatus.AVAILABLE) { + if (isAll4kSeasons && this.enable4kShow) { this.requestUpdater(newMedia.id, true); } await mediaRepository.save(newMedia); diff --git a/server/subscriber/MediaSubscriber.ts b/server/subscriber/MediaSubscriber.ts index eecfe6f3..cc6444ad 100644 --- a/server/subscriber/MediaSubscriber.ts +++ b/server/subscriber/MediaSubscriber.ts @@ -33,7 +33,9 @@ export class MediaSubscriber implements EntitySubscriberInterface { id: entity.id, }, is4k, - status: Not(MediaRequestStatus.DECLINED), + status: Not( + MediaRequestStatus.DECLINED && MediaRequestStatus.COMPLETED + ), }, }); @@ -116,7 +118,9 @@ export class MediaSubscriber implements EntitySubscriberInterface { id: entity.id, }, is4k, - status: Not(MediaRequestStatus.DECLINED), + status: Not( + MediaRequestStatus.DECLINED && MediaRequestStatus.COMPLETED + ), }, }); const request = requests.find( diff --git a/src/components/Discover/RecentRequestsSlider/index.tsx b/src/components/Discover/RecentRequestsSlider/index.tsx index 72909c8d..96309d61 100644 --- a/src/components/Discover/RecentRequestsSlider/index.tsx +++ b/src/components/Discover/RecentRequestsSlider/index.tsx @@ -21,8 +21,6 @@ const RecentRequestsSlider = () => { return null; } - console.log({ requests }); - return ( <>
diff --git a/src/components/RequestList/index.tsx b/src/components/RequestList/index.tsx index b01e36f5..1d464ce1 100644 --- a/src/components/RequestList/index.tsx +++ b/src/components/RequestList/index.tsx @@ -100,10 +100,6 @@ const RequestList = () => { ); }, [currentFilter, currentSort, currentPageSize]); - console.log({ currentFilter }); - - console.log({ data }); - if (!data && !error) { return ; } diff --git a/src/components/RequestModal/CollectionRequestModal.tsx b/src/components/RequestModal/CollectionRequestModal.tsx index 614a00da..75d7b8f4 100644 --- a/src/components/RequestModal/CollectionRequestModal.tsx +++ b/src/components/RequestModal/CollectionRequestModal.tsx @@ -78,7 +78,8 @@ const CollectionRequestModal = ({ .filter( (request) => request.is4k === is4k && - request.status !== MediaRequestStatus.DECLINED + request.status !== MediaRequestStatus.DECLINED && + request.status !== MediaRequestStatus.COMPLETED ) .map((part) => part.id), ]; @@ -167,7 +168,9 @@ const CollectionRequestModal = ({ return (part?.mediaInfo?.requests ?? []).find( (request) => - request.is4k === is4k && request.status !== MediaRequestStatus.DECLINED + request.is4k === is4k && + request.status !== MediaRequestStatus.DECLINED && + request.status !== MediaRequestStatus.COMPLETED ); }; @@ -241,6 +244,9 @@ const CollectionRequestModal = ({ { type: 'or' } ); + console.log('getALL', getAllRequestedParts().includes(8011)); + console.log('getPART', getPartRequest(8011)); + return ( request.is4k === is4k && - request.status !== MediaRequestStatus.DECLINED + request.status !== MediaRequestStatus.DECLINED && + request.status !== MediaRequestStatus.COMPLETED ) .reduce((requestedSeasons, request) => { return [ @@ -341,7 +342,8 @@ const TvRequestModal = ({ (data.mediaInfo.requests || []).filter( (request) => request.is4k === is4k && - request.status !== MediaRequestStatus.DECLINED + request.status !== MediaRequestStatus.DECLINED && + request.status !== MediaRequestStatus.COMPLETED ).length > 0 ) { data.mediaInfo.requests @@ -349,7 +351,9 @@ const TvRequestModal = ({ .forEach((request) => { if (!seasonRequest) { seasonRequest = request.seasons.find( - (season) => season.seasonNumber === seasonNumber + (season) => + season.seasonNumber === seasonNumber && + season.status !== MediaRequestStatus.COMPLETED ); } }); From af6c88b5e3001478554ae53fc5b87fa1ce3924c2 Mon Sep 17 00:00:00 2001 From: Brandon Date: Sat, 18 Mar 2023 19:41:47 -0400 Subject: [PATCH 05/13] fix: modified components to display deleted as a status --- src/components/Discover/RecentRequestsSlider/index.tsx | 2 ++ src/components/RequestList/index.tsx | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/components/Discover/RecentRequestsSlider/index.tsx b/src/components/Discover/RecentRequestsSlider/index.tsx index 96309d61..72909c8d 100644 --- a/src/components/Discover/RecentRequestsSlider/index.tsx +++ b/src/components/Discover/RecentRequestsSlider/index.tsx @@ -21,6 +21,8 @@ const RecentRequestsSlider = () => { return null; } + console.log({ requests }); + return ( <>
diff --git a/src/components/RequestList/index.tsx b/src/components/RequestList/index.tsx index 1d464ce1..b01e36f5 100644 --- a/src/components/RequestList/index.tsx +++ b/src/components/RequestList/index.tsx @@ -100,6 +100,10 @@ const RequestList = () => { ); }, [currentFilter, currentSort, currentPageSize]); + console.log({ currentFilter }); + + console.log({ data }); + if (!data && !error) { return ; } From ca01b60e5e0a63425348e2196567d2ded6ec6761 Mon Sep 17 00:00:00 2001 From: Brandon Date: Thu, 11 May 2023 13:01:42 -0400 Subject: [PATCH 06/13] fix: corrected media status switching away from deleted --- src/components/Discover/RecentRequestsSlider/index.tsx | 2 -- src/components/RequestList/index.tsx | 4 ---- 2 files changed, 6 deletions(-) diff --git a/src/components/Discover/RecentRequestsSlider/index.tsx b/src/components/Discover/RecentRequestsSlider/index.tsx index 72909c8d..96309d61 100644 --- a/src/components/Discover/RecentRequestsSlider/index.tsx +++ b/src/components/Discover/RecentRequestsSlider/index.tsx @@ -21,8 +21,6 @@ const RecentRequestsSlider = () => { return null; } - console.log({ requests }); - return ( <>
diff --git a/src/components/RequestList/index.tsx b/src/components/RequestList/index.tsx index b01e36f5..1d464ce1 100644 --- a/src/components/RequestList/index.tsx +++ b/src/components/RequestList/index.tsx @@ -100,10 +100,6 @@ const RequestList = () => { ); }, [currentFilter, currentSort, currentPageSize]); - console.log({ currentFilter }); - - console.log({ data }); - if (!data && !error) { return ; } From 390000d6d45ff2c4f60476dab567645787ac9e5a Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 12 May 2023 11:52:30 -0400 Subject: [PATCH 07/13] fix: base scanner will set requests to completed correctly --- server/lib/availabilitySync.ts | 1 + server/lib/scanners/baseScanner.ts | 68 ++++++++++++++++--- .../RequestModal/CollectionRequestModal.tsx | 3 - 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/server/lib/availabilitySync.ts b/server/lib/availabilitySync.ts index 398701bc..ea115c53 100644 --- a/server/lib/availabilitySync.ts +++ b/server/lib/availabilitySync.ts @@ -238,6 +238,7 @@ class AvailabilitySync { where: whereOptions, skip: offset, take: pageSize, + order: { id: 'DESC' }, })); offset += pageSize; } while (mediaPage.length > 0); diff --git a/server/lib/scanners/baseScanner.ts b/server/lib/scanners/baseScanner.ts index 26f26daf..25d3898c 100644 --- a/server/lib/scanners/baseScanner.ts +++ b/server/lib/scanners/baseScanner.ts @@ -13,6 +13,7 @@ import { getSettings } from '@server/lib/settings'; import logger from '@server/logger'; import AsyncLock from '@server/utils/asyncLock'; import { randomUUID } from 'crypto'; +import { In } from 'typeorm'; // Default scan rates (can be overidden) const BUNDLE_SIZE = 20; @@ -98,17 +99,25 @@ class BaseScanner { private async requestUpdater(mediaId: number, is4k: boolean) { const requestRepository = getRepository(MediaRequest); - const request = await requestRepository.findOne({ + const request = await requestRepository.find({ relations: { media: true, }, - where: { media: { id: mediaId }, is4k: is4k }, + where: { + media: { id: mediaId }, + is4k: is4k, + status: MediaRequestStatus.APPROVED, + }, }); - await requestRepository.update( - { id: request?.id }, - { status: MediaRequestStatus.COMPLETED } - ); + const requestIds = request.map((request) => request.id); + + if (requestIds.length > 0) { + await requestRepository.update( + { id: In(requestIds) }, + { status: MediaRequestStatus.COMPLETED } + ); + } } private async seasonRequestUpdater( @@ -369,7 +378,7 @@ class BaseScanner { ? MediaStatus.PARTIALLY_AVAILABLE : season.is4kOverride && season.processing && - existingSeason.status !== MediaStatus.DELETED + existingSeason.status4k !== MediaStatus.DELETED ? MediaStatus.PROCESSING : existingSeason.status4k; @@ -516,7 +525,6 @@ class BaseScanner { season.status4k !== MediaStatus.UNKNOWN && season.status4k !== MediaStatus.DELETED ).length === 0; - media.status = isAllStandardSeasons || shouldStayAvailable ? MediaStatus.AVAILABLE @@ -553,11 +561,51 @@ class BaseScanner { ? MediaStatus.DELETED : MediaStatus.UNKNOWN; - if (isAllStandardSeasons) { + const seasonsCompleted = + seasons.length && + seasons.filter( + (season) => + season.episodes === season.totalEpisodes && season.episodes > 0 + ).length; + + const seasonsCompleted4k = + seasons.length && + seasons.filter( + (season) => + season.episodes4k === season.totalEpisodes && + season.episodes4k > 0 + ).length; + + const seasonRequestRepository = getRepository(SeasonRequest); + + const seasonRequestsCompleted = await seasonRequestRepository.find({ + relations: { + request: { + media: true, + }, + }, + where: { + request: { + is4k: is4k, + media: { + id: media.id, + }, + }, + status: MediaRequestStatus.COMPLETED, + }, + }); + + if ( + seasonsCompleted === seasonRequestsCompleted.length && + seasonRequestsCompleted.length > 0 + ) { this.requestUpdater(media.id, false); } - if (isAll4kSeasons) { + if ( + seasonsCompleted4k === seasonRequestsCompleted.length && + seasonRequestsCompleted.length > 0 + ) { this.requestUpdater(media.id, true); } await mediaRepository.save(media); diff --git a/src/components/RequestModal/CollectionRequestModal.tsx b/src/components/RequestModal/CollectionRequestModal.tsx index 75d7b8f4..b650d07d 100644 --- a/src/components/RequestModal/CollectionRequestModal.tsx +++ b/src/components/RequestModal/CollectionRequestModal.tsx @@ -244,9 +244,6 @@ const CollectionRequestModal = ({ { type: 'or' } ); - console.log('getALL', getAllRequestedParts().includes(8011)); - console.log('getPART', getPartRequest(8011)); - return ( Date: Fri, 12 May 2023 18:42:54 -0400 Subject: [PATCH 08/13] fix: mark available button correctly sets requests as completed --- server/lib/availabilitySync.ts | 1 - server/routes/media.ts | 24 +++++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/server/lib/availabilitySync.ts b/server/lib/availabilitySync.ts index ea115c53..398701bc 100644 --- a/server/lib/availabilitySync.ts +++ b/server/lib/availabilitySync.ts @@ -238,7 +238,6 @@ class AvailabilitySync { where: whereOptions, skip: offset, take: pageSize, - order: { id: 'DESC' }, })); offset += pageSize; } while (mediaPage.length > 0); diff --git a/server/routes/media.ts b/server/routes/media.ts index 9987380d..ee0bc4f4 100644 --- a/server/routes/media.ts +++ b/server/routes/media.ts @@ -147,23 +147,29 @@ mediaRoutes.post< } if (req.params.status === 'available') { - const request = await requestRepository.findOne({ + const requests = await requestRepository.find({ relations: { media: true, }, where: { media: { id: media.id }, is4k: is4k }, }); - await requestRepository.update( - { media: { id: request?.id } }, - { status: MediaRequestStatus.COMPLETED } - ); + const requestIds = requests.map((request) => request.id); - request?.seasons.forEach(async (season) => { - await seasonRequestRepository.update(season.id, { - status: MediaRequestStatus.COMPLETED, + if (requestIds.length > 0) { + await requestRepository.update( + { id: In(requestIds) }, + { status: MediaRequestStatus.COMPLETED } + ); + } + + requests + .flatMap((request) => request.seasons) + .forEach(async (season) => { + await seasonRequestRepository.update(season.id, { + status: MediaRequestStatus.COMPLETED, + }); }); - }); } await mediaRepository.save(media); From 5c3ea9b03625d3663205e8d561313b2fe22bb1d2 Mon Sep 17 00:00:00 2001 From: Brandon Cohen Date: Sun, 14 May 2023 22:53:12 -0400 Subject: [PATCH 09/13] fix: status will now stay deleted after declined request --- server/entity/MediaRequest.ts | 16 +++++++++++----- server/routes/request.ts | 3 ++- src/components/RequestButton/index.tsx | 4 ++-- src/components/RequestCard/index.tsx | 11 ++++++++++- src/components/RequestList/RequestItem/index.tsx | 11 ++++++++++- src/components/TitleCard/index.tsx | 11 ++++++++--- src/components/TvDetails/index.tsx | 3 ++- 7 files changed, 45 insertions(+), 14 deletions(-) diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index ba67ab7b..4597b3ed 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -167,7 +167,8 @@ export class MediaRequest { // If there is an existing movie request that isn't declined, don't allow a new one. if ( requestBody.mediaType === MediaType.MOVIE && - existing[0].status !== MediaRequestStatus.DECLINED + existing[0].status !== MediaRequestStatus.DECLINED && + existing[0].status !== MediaRequestStatus.COMPLETED ) { logger.warn('Duplicate request for media blocked', { tmdbId: tmdbMedia.id, @@ -260,7 +261,8 @@ export class MediaRequest { .filter( (request) => request.is4k === requestBody.is4k && - request.status !== MediaRequestStatus.DECLINED + request.status !== MediaRequestStatus.DECLINED && + request.status !== MediaRequestStatus.COMPLETED ) .reduce((seasons, request) => { const combinedSeasons = request.seasons.map( @@ -279,7 +281,9 @@ export class MediaRequest { .filter( (season) => season[requestBody.is4k ? 'status4k' : 'status'] !== - MediaStatus.UNKNOWN + MediaStatus.UNKNOWN && + season[requestBody.is4k ? 'status4k' : 'status'] !== + MediaStatus.DELETED ) .map((season) => season.seasonNumber), ]; @@ -583,7 +587,8 @@ export class MediaRequest { if ( media.mediaType === MediaType.MOVIE && - this.status === MediaRequestStatus.DECLINED + this.status === MediaRequestStatus.DECLINED && + media[this.is4k ? 'status4k' : 'status'] !== MediaStatus.DELETED ) { media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN; mediaRepository.save(media); @@ -601,7 +606,8 @@ export class MediaRequest { media.requests.filter( (request) => request.status === MediaRequestStatus.PENDING ).length === 0 && - media[this.is4k ? 'status4k' : 'status'] === MediaStatus.PENDING + media[this.is4k ? 'status4k' : 'status'] === MediaStatus.PENDING && + media[this.is4k ? 'status4k' : 'status'] !== MediaStatus.DELETED ) { media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN; mediaRepository.save(media); diff --git a/server/routes/request.ts b/server/routes/request.ts index 1f8fcf66..18cb6458 100644 --- a/server/routes/request.ts +++ b/server/routes/request.ts @@ -400,7 +400,8 @@ requestRoutes.put<{ requestId: string }>( (r) => r.is4k === request.is4k && r.id !== request.id && - r.status !== MediaRequestStatus.DECLINED + r.status !== MediaRequestStatus.DECLINED && + r.status !== MediaRequestStatus.COMPLETED ) .reduce((seasons, r) => { const combinedSeasons = r.seasons.map( diff --git a/src/components/RequestButton/index.tsx b/src/components/RequestButton/index.tsx index b41a5fde..c614b5d8 100644 --- a/src/components/RequestButton/index.tsx +++ b/src/components/RequestButton/index.tsx @@ -266,7 +266,7 @@ const RequestButton = ({ if ( (!media || media.status === MediaStatus.UNKNOWN || - media.status === MediaStatus.DELETED) && + (media.status === MediaStatus.DELETED && !activeRequest)) && hasPermission( [ Permission.REQUEST, @@ -311,7 +311,7 @@ const RequestButton = ({ if ( (!media || media.status4k === MediaStatus.UNKNOWN || - media.status4k === MediaStatus.DELETED) && + (media.status4k === MediaStatus.DELETED && !active4kRequest)) && hasPermission( [ Permission.REQUEST_4K, diff --git a/src/components/RequestCard/index.tsx b/src/components/RequestCard/index.tsx index 44abd555..b934e907 100644 --- a/src/components/RequestCard/index.tsx +++ b/src/components/RequestCard/index.tsx @@ -16,7 +16,7 @@ import { TrashIcon, XMarkIcon, } from '@heroicons/react/24/solid'; -import { MediaRequestStatus } from '@server/constants/media'; +import { MediaRequestStatus, MediaStatus } from '@server/constants/media'; import type { MediaRequest } from '@server/entity/MediaRequest'; import type { MovieDetails } from '@server/models/Movie'; import type { TvDetails } from '@server/models/Tv'; @@ -411,6 +411,15 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => { > {intl.formatMessage(globalMessages.failed)} + ) : requestData.status === MediaRequestStatus.PENDING && + requestData.media[requestData.is4k ? 'status4k' : 'status'] === + MediaStatus.DELETED ? ( + + {intl.formatMessage(globalMessages.pending)} + ) : ( { > {intl.formatMessage(globalMessages.failed)} + ) : requestData.status === MediaRequestStatus.PENDING && + requestData.media[requestData.is4k ? 'status4k' : 'status'] === + MediaStatus.DELETED ? ( + + {intl.formatMessage(globalMessages.pending)} + ) : ( {showRequestButton && - (!currentStatus || currentStatus === MediaStatus.UNKNOWN) && ( + (!currentStatus || + currentStatus === MediaStatus.UNKNOWN || + currentStatus === MediaStatus.DELETED) && (