mirror of
https://github.com/sct/overseerr.git
synced 2025-12-27 00:34:56 +01:00
refactor: improve reliability of season request completion
refactor: remove seasonrequest code
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
MediaType,
|
||||
} from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import SeasonRequest from '@server/entity/SeasonRequest';
|
||||
import type { DownloadingItem } from '@server/lib/downloadtracker';
|
||||
import downloadTracker from '@server/lib/downloadtracker';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
@@ -318,12 +319,17 @@ class Media {
|
||||
@AfterUpdate()
|
||||
public async updateRelatedMediaRequest(): Promise<void> {
|
||||
const requestRepository = getRepository(MediaRequest);
|
||||
const seasonRequestRepository = getRepository(SeasonRequest);
|
||||
|
||||
const validStatuses = [
|
||||
MediaStatus.PARTIALLY_AVAILABLE,
|
||||
MediaStatus.AVAILABLE,
|
||||
MediaStatus.DELETED,
|
||||
];
|
||||
|
||||
if (
|
||||
this.status === MediaStatus.AVAILABLE ||
|
||||
this.status === MediaStatus.DELETED ||
|
||||
this.status4k === MediaStatus.AVAILABLE ||
|
||||
this.status4k === MediaStatus.DELETED
|
||||
validStatuses.includes(this.status) ||
|
||||
validStatuses.includes(this.status4k)
|
||||
) {
|
||||
const relatedRequests = await requestRepository.find({
|
||||
relations: {
|
||||
@@ -338,16 +344,79 @@ class Media {
|
||||
// Check the media entity status and if available
|
||||
// or deleted, set the related request to completed
|
||||
if (relatedRequests.length > 0) {
|
||||
const completedRequests: MediaRequest[] = [];
|
||||
|
||||
relatedRequests.forEach((request) => {
|
||||
let shouldComplete = false;
|
||||
|
||||
if (
|
||||
this[request.is4k ? 'status4k' : 'status'] ===
|
||||
MediaStatus.AVAILABLE ||
|
||||
this[request.is4k ? 'status4k' : 'status'] === MediaStatus.DELETED
|
||||
) {
|
||||
shouldComplete = true;
|
||||
} else if (this.mediaType === 'tv') {
|
||||
// For TV, check if all requested seasons are available or deleted
|
||||
const allSeasonsReady = request.seasons.every((requestSeason) => {
|
||||
const matchingSeason = this.seasons.find(
|
||||
(mediaSeason) =>
|
||||
mediaSeason.seasonNumber === requestSeason.seasonNumber
|
||||
);
|
||||
|
||||
if (!matchingSeason) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
matchingSeason[request.is4k ? 'status4k' : 'status'] ===
|
||||
MediaStatus.AVAILABLE ||
|
||||
matchingSeason[request.is4k ? 'status4k' : 'status'] ===
|
||||
MediaStatus.DELETED
|
||||
);
|
||||
});
|
||||
|
||||
shouldComplete = allSeasonsReady;
|
||||
}
|
||||
|
||||
if (shouldComplete) {
|
||||
request.status = MediaRequestStatus.COMPLETED;
|
||||
completedRequests.push(request);
|
||||
}
|
||||
});
|
||||
requestRepository.save(relatedRequests);
|
||||
|
||||
await requestRepository.save(completedRequests);
|
||||
|
||||
// Handle season requests and mark them completed when
|
||||
// that specific season becomes available
|
||||
if (this.mediaType === 'tv') {
|
||||
const seasonsToUpdate = relatedRequests.flatMap((request) => {
|
||||
return request.seasons.filter((requestSeason) => {
|
||||
const matchingSeason = this.seasons.find(
|
||||
(mediaSeason) =>
|
||||
mediaSeason.seasonNumber === requestSeason.seasonNumber
|
||||
);
|
||||
|
||||
if (!matchingSeason) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
matchingSeason[request.is4k ? 'status4k' : 'status'] ===
|
||||
MediaStatus.AVAILABLE ||
|
||||
matchingSeason[request.is4k ? 'status4k' : 'status'] ===
|
||||
MediaStatus.DELETED
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
seasonsToUpdate.map((season) =>
|
||||
seasonRequestRepository.update(season.id, {
|
||||
status: MediaRequestStatus.COMPLETED,
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { MediaRequestStatus, MediaStatus } from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import SeasonRequest from '@server/entity/SeasonRequest';
|
||||
import { MediaStatus } from '@server/constants/media';
|
||||
import {
|
||||
AfterInsert,
|
||||
AfterUpdate,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
@@ -39,45 +35,6 @@ class Season {
|
||||
constructor(init?: Partial<Season>) {
|
||||
Object.assign(this, init);
|
||||
}
|
||||
|
||||
@AfterInsert()
|
||||
@AfterUpdate()
|
||||
public async updateSeasonRequests(): Promise<void> {
|
||||
const seasonRequestRepository = getRepository(SeasonRequest);
|
||||
|
||||
if (
|
||||
this.status === MediaStatus.AVAILABLE ||
|
||||
this.status === MediaStatus.DELETED ||
|
||||
this.status4k === MediaStatus.AVAILABLE ||
|
||||
this.status4k === MediaStatus.DELETED
|
||||
) {
|
||||
const relatedSeasonRequests = await seasonRequestRepository.find({
|
||||
relations: {
|
||||
request: true,
|
||||
},
|
||||
where: {
|
||||
request: { media: { id: (await this.media).id } },
|
||||
seasonNumber: this.seasonNumber,
|
||||
},
|
||||
});
|
||||
|
||||
// Check seasons when/if they become available or deleted,
|
||||
// then set the related season request to completed
|
||||
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;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { MediaRequestStatus } from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import {
|
||||
AfterUpdate,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
@@ -36,28 +34,6 @@ class SeasonRequest {
|
||||
constructor(init?: Partial<SeasonRequest>) {
|
||||
Object.assign(this, init);
|
||||
}
|
||||
|
||||
@AfterUpdate()
|
||||
public async updateMediaRequests(): Promise<void> {
|
||||
const requestRepository = getRepository(MediaRequest);
|
||||
|
||||
const relatedRequest = await requestRepository.findOne({
|
||||
where: { id: this.request.id },
|
||||
});
|
||||
|
||||
// Check the parent of the season request and
|
||||
// if every season request is complete
|
||||
// set the parent request to complete as well
|
||||
const isRequestComplete = relatedRequest?.seasons.every(
|
||||
(seasonRequest) => seasonRequest.status === MediaRequestStatus.COMPLETED
|
||||
);
|
||||
|
||||
if (isRequestComplete && relatedRequest) {
|
||||
relatedRequest.status = MediaRequestStatus.COMPLETED;
|
||||
|
||||
requestRepository.save(relatedRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SeasonRequest;
|
||||
|
||||
@@ -247,7 +247,7 @@ class AvailabilitySync {
|
||||
|
||||
try {
|
||||
// If media type is tv, check if a season is processing
|
||||
//to see if we need to keep the external metadata
|
||||
// to see if we need to keep the external metadata
|
||||
let isMediaProcessing = false;
|
||||
|
||||
if (media.mediaType === 'tv') {
|
||||
@@ -386,44 +386,44 @@ 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) {
|
||||
if (server.is4k === is4k) {
|
||||
const radarrAPI = new RadarrAPI({
|
||||
apiKey: server.apiKey,
|
||||
url: RadarrAPI.buildUrl(server, '/api/v3'),
|
||||
});
|
||||
for (const server of this.radarrServers.filter(
|
||||
(server) => 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 (media.externalServiceId && !is4k) {
|
||||
radarr = await radarrAPI.getMovie({
|
||||
id: media.externalServiceId,
|
||||
});
|
||||
}
|
||||
if (media.externalServiceId && !is4k) {
|
||||
radarr = await radarrAPI.getMovie({
|
||||
id: media.externalServiceId,
|
||||
});
|
||||
}
|
||||
|
||||
if (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',
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -440,46 +440,45 @@ 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) {
|
||||
if (server.is4k === is4k) {
|
||||
const sonarrAPI = new SonarrAPI({
|
||||
apiKey: server.apiKey,
|
||||
url: SonarrAPI.buildUrl(server, '/api/v3'),
|
||||
});
|
||||
for (const server of this.sonarrServers.filter((server) => {
|
||||
return 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 (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 (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',
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -523,7 +522,9 @@ class AvailabilitySync {
|
||||
// Check each sonarr instance to see if the media still exists
|
||||
// If found, we will assume the media exists and prevent removal
|
||||
// We can use the cache we built when we fetched the series with mediaExistsInSonarr
|
||||
for (const server of this.sonarrServers) {
|
||||
for (const server of this.sonarrServers.filter(
|
||||
(server) => server.is4k === is4k
|
||||
)) {
|
||||
let sonarrSeasons: SonarrSeason[] | undefined;
|
||||
|
||||
if (media.externalServiceId && !is4k) {
|
||||
|
||||
@@ -584,8 +584,6 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log({ request });
|
||||
|
||||
return (
|
||||
<Disclosure key={`season-discoslure-${season.seasonNumber}`}>
|
||||
{({ open }) => (
|
||||
|
||||
Reference in New Issue
Block a user