refactor: request completion handling moved to entity

pull/3460/head
Brandon Cohen 1 year ago committed by OwsleyJr
parent 5c3ea9b036
commit 82bf2aa4df

@ -1,6 +1,10 @@
import RadarrAPI from '@server/api/servarr/radarr';
import SonarrAPI from '@server/api/servarr/sonarr';
import { MediaStatus, MediaType } from '@server/constants/media';
import {
MediaRequestStatus,
MediaStatus,
MediaType,
} from '@server/constants/media';
import { getRepository } from '@server/datasource';
import type { DownloadingItem } from '@server/lib/downloadtracker';
import downloadTracker from '@server/lib/downloadtracker';
@ -8,6 +12,7 @@ import { getSettings } from '@server/lib/settings';
import logger from '@server/logger';
import {
AfterLoad,
AfterUpdate,
Column,
CreateDateColumn,
Entity,
@ -309,6 +314,34 @@ class Media {
}
}
}
@AfterUpdate()
public async updateRelatedMediaRequest(): Promise<void> {
const requestRepository = getRepository(MediaRequest);
const relatedRequests = await requestRepository.find({
relations: {
media: true,
},
where: {
media: { id: this.id },
status: MediaRequestStatus.APPROVED,
},
});
if (relatedRequests.length > 0) {
relatedRequests.forEach((request) => {
if (
this[request.is4k ? 'status4k' : 'status'] ===
MediaStatus.AVAILABLE ||
this[request.is4k ? 'status4k' : 'status'] === MediaStatus.DELETED
) {
request.status = MediaRequestStatus.COMPLETED;
}
});
requestRepository.save(relatedRequests);
}
}
}
export default Media;

@ -1,5 +1,8 @@
import { MediaStatus } from '@server/constants/media';
import { MediaRequestStatus, MediaStatus } from '@server/constants/media';
import { getRepository } from '@server/datasource';
import SeasonRequest from '@server/entity/SeasonRequest';
import {
AfterUpdate,
Column,
CreateDateColumn,
Entity,
@ -35,6 +38,35 @@ class Season {
constructor(init?: Partial<Season>) {
Object.assign(this, init);
}
@AfterUpdate()
public async updateSeasonRequests(): Promise<void> {
const seasonRequestRepository = getRepository(SeasonRequest);
const relatedSeasonRequests = await seasonRequestRepository.find({
relations: {
request: true,
},
where: {
request: { media: { id: (await this.media).id } },
seasonNumber: this.seasonNumber,
},
});
relatedSeasonRequests.forEach((seasonRequest) => {
if (
this.seasonNumber === seasonRequest.seasonNumber &&
((!seasonRequest.request.is4k &&
(this.status === MediaStatus.AVAILABLE ||
this.status === MediaStatus.DELETED)) ||
(seasonRequest.request.is4k &&
this.status4k === MediaStatus.AVAILABLE) ||
this.status4k === MediaStatus.DELETED)
)
seasonRequest.status = MediaRequestStatus.COMPLETED;
});
seasonRequestRepository.save(relatedSeasonRequests);
}
}
export default Season;

@ -38,22 +38,21 @@ class SeasonRequest {
}
@AfterUpdate()
public async handleRemoveParent(): Promise<void> {
const mediaRequestRepository = getRepository(MediaRequest);
const requestToBeDeleted = await mediaRequestRepository.findOneOrFail({
public async updateMediaRequests(): Promise<void> {
const requestRepository = getRepository(MediaRequest);
const relatedRequest = await requestRepository.findOne({
where: { id: this.request.id },
});
const allSeasonsAreCompleted = requestToBeDeleted.seasons.filter(
(season) => {
return season.status === MediaRequestStatus.COMPLETED;
}
const isRequestComplete = relatedRequest?.seasons.every(
(seasonRequest) => seasonRequest.status === MediaRequestStatus.COMPLETED
);
if (requestToBeDeleted.seasons.length === allSeasonsAreCompleted.length) {
await mediaRequestRepository.update(this.request.id, {
status: MediaRequestStatus.COMPLETED,
});
if (isRequestComplete && relatedRequest) {
relatedRequest.status = MediaRequestStatus.COMPLETED;
requestRepository.save(relatedRequest);
}
}
}

@ -3,7 +3,7 @@ import PlexAPI from '@server/api/plexapi';
import RadarrAPI, { type RadarrMovie } from '@server/api/servarr/radarr';
import type { SonarrSeason, SonarrSeries } from '@server/api/servarr/sonarr';
import SonarrAPI from '@server/api/servarr/sonarr';
import { MediaRequestStatus, MediaStatus } from '@server/constants/media';
import { MediaStatus } from '@server/constants/media';
import { getRepository } from '@server/datasource';
import Media from '@server/entity/Media';
import MediaRequest from '@server/entity/MediaRequest';
@ -203,6 +203,9 @@ class AvailabilitySync {
await this.mediaUpdater(media, true);
}
}
if (!mediaExists || didDeleteSeasons) {
await mediaRepository.save(media);
}
}
} catch (ex) {
logger.error('Failed to complete availability sync.', {

@ -1,19 +1,12 @@
import TheMovieDb from '@server/api/themoviedb';
import {
MediaRequestStatus,
MediaStatus,
MediaType,
} from '@server/constants/media';
import { 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';
import { randomUUID } from 'crypto';
import { In } from 'typeorm';
// Default scan rates (can be overidden)
const BUNDLE_SIZE = 20;
@ -96,61 +89,6 @@ class BaseScanner<T> {
return existing;
}
private async requestUpdater(mediaId: number, is4k: boolean) {
const requestRepository = getRepository(MediaRequest);
const request = await requestRepository.find({
relations: {
media: true,
},
where: {
media: { id: mediaId },
is4k: is4k,
status: MediaRequestStatus.APPROVED,
},
});
const requestIds = request.map((request) => request.id);
if (requestIds.length > 0) {
await requestRepository.update(
{ id: In(requestIds) },
{ 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,
{
@ -224,14 +162,6 @@ class BaseScanner<T> {
}
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(
@ -275,14 +205,6 @@ class BaseScanner<T> {
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}`);
}
@ -381,22 +303,6 @@ class BaseScanner<T> {
existingSeason.status4k !== MediaStatus.DELETED
? MediaStatus.PROCESSING
: existingSeason.status4k;
if (
(season.totalEpisodes === season.episodes && season.episodes > 0) ||
existingSeason.status === MediaStatus.AVAILABLE
) {
this.seasonRequestUpdater(media?.id, season.seasonNumber, false);
}
if (
(this.enable4kShow &&
season.episodes4k === season.totalEpisodes &&
season.episodes4k > 0) ||
existingSeason.status4k === MediaStatus.AVAILABLE
) {
this.seasonRequestUpdater(media?.id, season.seasonNumber, true);
}
} else {
newSeasons.push(
new Season({
@ -421,18 +327,6 @@ class BaseScanner<T> {
: 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);
}
}
}
@ -561,53 +455,6 @@ class BaseScanner<T> {
? MediaStatus.DELETED
: MediaStatus.UNKNOWN;
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 (
seasonsCompleted4k === seasonRequestsCompleted.length &&
seasonRequestsCompleted.length > 0
) {
this.requestUpdater(media.id, true);
}
await mediaRepository.save(media);
this.log(`Updating existing title: ${title}`);
@ -670,13 +517,6 @@ class BaseScanner<T> {
: MediaStatus.UNKNOWN,
});
if (isAllStandardSeasons) {
this.requestUpdater(newMedia.id, false);
}
if (isAll4kSeasons && this.enable4kShow) {
this.requestUpdater(newMedia.id, true);
}
await mediaRepository.save(newMedia);
this.log(`Saved ${title}`);

@ -12,7 +12,7 @@ import notificationManager, { Notification } from '@server/lib/notifications';
import logger from '@server/logger';
import { truncate } from 'lodash';
import type { EntitySubscriberInterface, UpdateEvent } from 'typeorm';
import { EventSubscriber, In, Not } from 'typeorm';
import { EventSubscriber, In } from 'typeorm';
@EventSubscriber()
export class MediaSubscriber implements EntitySubscriberInterface<Media> {
@ -27,47 +27,39 @@ export class MediaSubscriber implements EntitySubscriberInterface<Media> {
) {
if (entity.mediaType === MediaType.MOVIE) {
const requestRepository = getRepository(MediaRequest);
const relatedRequests = await requestRepository.find({
const relatedRequest = await requestRepository.findOne({
where: {
media: {
id: entity.id,
},
is4k,
status: Not(
MediaRequestStatus.DECLINED && MediaRequestStatus.COMPLETED
),
status: MediaRequestStatus.COMPLETED,
},
order: { id: 'DESC' },
});
if (relatedRequests.length > 0) {
const tmdb = new TheMovieDb();
const tmdb = new TheMovieDb();
if (relatedRequest) {
try {
const movie = await tmdb.getMovie({ movieId: entity.tmdbId });
relatedRequests.forEach((request) => {
notificationManager.sendNotification(
Notification.MEDIA_AVAILABLE,
{
event: `${is4k ? '4K ' : ''}Movie Request Now Available`,
notifyAdmin: false,
notifySystem: true,
notifyUser: request.requestedBy,
subject: `${movie.title}${
movie.release_date
? ` (${movie.release_date.slice(0, 4)})`
: ''
}`,
message: truncate(movie.overview, {
length: 500,
separator: /\s/,
omission: '…',
}),
media: entity,
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`,
request,
}
);
notificationManager.sendNotification(Notification.MEDIA_AVAILABLE, {
event: `${is4k ? '4K ' : ''}Movie Request Now Available`,
notifyAdmin: false,
notifySystem: true,
notifyUser: relatedRequest.requestedBy,
subject: `${movie.title}${
movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : ''
}`,
message: truncate(movie.overview, {
length: 500,
separator: /\s/,
omission: '…',
}),
media: entity,
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`,
request: relatedRequest,
});
} catch (e) {
logger.error('Something went wrong sending media notification(s)', {
@ -118,10 +110,9 @@ export class MediaSubscriber implements EntitySubscriberInterface<Media> {
id: entity.id,
},
is4k,
status: Not(
MediaRequestStatus.DECLINED && MediaRequestStatus.COMPLETED
),
status: MediaRequestStatus.COMPLETED,
},
order: { id: 'DESC' },
});
const request = requests.find(
(request) =>

Loading…
Cancel
Save