Skip to content

Commit

Permalink
feat: availability sync rework (#3219)
Browse files Browse the repository at this point in the history
* feat: add availability synchronization job

fix #377

* fix: feedback on PR

* perf: use pagination for Media Availability Synchronization job

The original approach loaded all media items from the database at once. With large libraries, this
could lead to performance issues. We're now using a paginated approach with a page size of 50.

* feat: updated the availability sync to work with 4k

* fix: corrected detection of media in plex

* refactor: code cleanup and minimized unnecessary calls

* fix: if media is not found, media check will continue

* fix: if non-4k or 4k show media is updated, seasons and request is now properly updated

* refactor: consolidated media updater into one function

* fix: season requests are now removed if season has been deleted

* refactor: removed joincolumn

* fix: makes sure we will always check radarr/sonarr to see if media exists

* fix: media will now only be updated to unavailable and deletion will be prevented

* fix: changed types in Media entity

* fix: prevent season deletion in preference of setting season to unknown

---------

Co-authored-by: Jari Zwarts <jari@oberon.nl>
Co-authored-by: Sebastian Kappen <sebastian@kappen.dev>
  • Loading branch information
3 people committed Feb 24, 2023
1 parent b3882de commit ae38183
Show file tree
Hide file tree
Showing 9 changed files with 788 additions and 22 deletions.
2 changes: 1 addition & 1 deletion server/api/servarr/sonarr.ts
@@ -1,7 +1,7 @@
import logger from '@server/logger';
import ServarrBase from './base';

interface SonarrSeason {
export interface SonarrSeason {
seasonNumber: number;
monitored: boolean;
statistics?: {
Expand Down
48 changes: 28 additions & 20 deletions server/entity/Media.ts
Expand Up @@ -114,29 +114,29 @@ class Media {
@Column({ type: 'datetime', nullable: true })
public mediaAddedAt: Date;

@Column({ nullable: true })
public serviceId?: number;
@Column({ nullable: true, type: 'int' })
public serviceId?: number | null;

@Column({ nullable: true })
public serviceId4k?: number;
@Column({ nullable: true, type: 'int' })
public serviceId4k?: number | null;

@Column({ nullable: true })
public externalServiceId?: number;
@Column({ nullable: true, type: 'int' })
public externalServiceId?: number | null;

@Column({ nullable: true })
public externalServiceId4k?: number;
@Column({ nullable: true, type: 'int' })
public externalServiceId4k?: number | null;

@Column({ nullable: true })
public externalServiceSlug?: string;
@Column({ nullable: true, type: 'varchar' })
public externalServiceSlug?: string | null;

@Column({ nullable: true })
public externalServiceSlug4k?: string;
@Column({ nullable: true, type: 'varchar' })
public externalServiceSlug4k?: string | null;

@Column({ nullable: true })
public ratingKey?: string;
@Column({ nullable: true, type: 'varchar' })
public ratingKey?: string | null;

@Column({ nullable: true })
public ratingKey4k?: string;
@Column({ nullable: true, type: 'varchar' })
public ratingKey4k?: string | null;

public serviceUrl?: string;
public serviceUrl4k?: string;
Expand Down Expand Up @@ -260,7 +260,9 @@ class Media {
if (this.mediaType === MediaType.MOVIE) {
if (
this.externalServiceId !== undefined &&
this.serviceId !== undefined
this.externalServiceId !== null &&
this.serviceId !== undefined &&
this.serviceId !== null
) {
this.downloadStatus = downloadTracker.getMovieProgress(
this.serviceId,
Expand All @@ -270,7 +272,9 @@ class Media {

if (
this.externalServiceId4k !== undefined &&
this.serviceId4k !== undefined
this.externalServiceId4k !== null &&
this.serviceId4k !== undefined &&
this.serviceId4k !== null
) {
this.downloadStatus4k = downloadTracker.getMovieProgress(
this.serviceId4k,
Expand All @@ -282,7 +286,9 @@ class Media {
if (this.mediaType === MediaType.TV) {
if (
this.externalServiceId !== undefined &&
this.serviceId !== undefined
this.externalServiceId !== null &&
this.serviceId !== undefined &&
this.serviceId !== null
) {
this.downloadStatus = downloadTracker.getSeriesProgress(
this.serviceId,
Expand All @@ -292,7 +298,9 @@ class Media {

if (
this.externalServiceId4k !== undefined &&
this.serviceId4k !== undefined
this.externalServiceId4k !== null &&
this.serviceId4k !== undefined &&
this.serviceId4k !== null
) {
this.downloadStatus4k = downloadTracker.getSeriesProgress(
this.serviceId4k,
Expand Down
2 changes: 2 additions & 0 deletions server/entity/MediaRequest.ts
Expand Up @@ -1187,3 +1187,5 @@ export class MediaRequest {
}
}
}

export default MediaRequest;
14 changes: 14 additions & 0 deletions server/entity/SeasonRequest.ts
@@ -1,5 +1,7 @@
import { MediaRequestStatus } from '@server/constants/media';
import { getRepository } from '@server/datasource';
import {
AfterRemove,
Column,
CreateDateColumn,
Entity,
Expand Down Expand Up @@ -34,6 +36,18 @@ class SeasonRequest {
constructor(init?: Partial<SeasonRequest>) {
Object.assign(this, init);
}

@AfterRemove()
public async handleRemoveParent(): Promise<void> {
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 });
}
}
}

export default SeasonRequest;
18 changes: 18 additions & 0 deletions server/job/schedule.ts
@@ -1,3 +1,4 @@
import availabilitySync from '@server/lib/availabilitySync';
import downloadTracker from '@server/lib/downloadtracker';
import ImageProxy from '@server/lib/imageproxy';
import { plexFullScanner, plexRecentScanner } from '@server/lib/scanners/plex';
Expand Down Expand Up @@ -104,6 +105,23 @@ export const startJobs = (): void => {
cancelFn: () => sonarrScanner.cancel(),
});

// Checks if media is still available in plex/sonarr/radarr libs
scheduledJobs.push({
id: 'availability-sync',
name: 'Media Availability Sync',
type: 'process',
interval: 'hours',
cronSchedule: jobs['availability-sync'].schedule,
job: schedule.scheduleJob(jobs['availability-sync'].schedule, () => {
logger.info('Starting scheduled job: Media Availability Sync', {
label: 'Jobs',
});
availabilitySync.run();
}),
running: () => availabilitySync.running,
cancelFn: () => availabilitySync.cancel(),
});

// Run download sync every minute
scheduledJobs.push({
id: 'download-sync',
Expand Down

0 comments on commit ae38183

Please sign in to comment.