refactor: request completion handling moved to entity
This commit is contained in:
@@ -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) =>
|
||||
|
||||
Reference in New Issue
Block a user