refactor: improve reliability of season request completion

refactor: remove seasonrequest code
This commit is contained in:
OwsleyJr
2025-04-08 13:37:49 -05:00
parent 5b0cf89391
commit e6e609200c
5 changed files with 148 additions and 147 deletions

View File

@@ -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,
})
)
);
}
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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) {

View File

@@ -584,8 +584,6 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
return null;
}
console.log({ request });
return (
<Disclosure key={`season-discoslure-${season.seasonNumber}`}>
{({ open }) => (