fix(plex-sync): store plex added date and sort recently added by it

This commit is contained in:
sct
2021-01-13 09:51:11 +00:00
parent a262727078
commit d688a96759
7 changed files with 86 additions and 6 deletions

View File

@@ -2976,7 +2976,7 @@ paths:
name: sort name: sort
schema: schema:
type: string type: string
enum: [added, modified] enum: [added, modified, mediaAdded]
default: added default: added
responses: responses:
'200': '200':

View File

@@ -9,6 +9,8 @@ export interface PlexLibraryItem {
guid: string; guid: string;
parentGuid?: string; parentGuid?: string;
grandparentGuid?: string; grandparentGuid?: string;
addedAt: number;
updatedAt: number;
type: 'movie' | 'show' | 'season' | 'episode'; type: 'movie' | 'show' | 'season' | 'episode';
} }
@@ -48,6 +50,8 @@ export interface PlexMetadata {
parentIndex?: number; parentIndex?: number;
leafCount: number; leafCount: number;
viewedLeafCount: number; viewedLeafCount: number;
addedAt: number;
updatedAt: number;
Media: Media[]; Media: Media[];
} }

View File

@@ -101,6 +101,9 @@ class Media {
@Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' }) @Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
public lastSeasonChange: Date; public lastSeasonChange: Date;
@Column({ type: 'datetime', nullable: true })
public mediaAddedAt: Date;
constructor(init?: Partial<Media>) { constructor(init?: Partial<Media>) {
Object.assign(this, init); Object.assign(this, init);
} }

View File

@@ -111,6 +111,7 @@ class JobPlexSync {
existing.status !== MediaStatus.AVAILABLE existing.status !== MediaStatus.AVAILABLE
) { ) {
existing.status = MediaStatus.AVAILABLE; existing.status = MediaStatus.AVAILABLE;
existing.mediaAddedAt = new Date(plexitem.addedAt * 1000);
changedExisting = true; changedExisting = true;
} }
@@ -123,6 +124,11 @@ class JobPlexSync {
changedExisting = true; changedExisting = true;
} }
if (!existing.mediaAddedAt && !changedExisting) {
existing.mediaAddedAt = new Date(plexitem.addedAt * 1000);
changedExisting = true;
}
if (changedExisting) { if (changedExisting) {
await mediaRepository.save(existing); await mediaRepository.save(existing);
this.log( this.log(
@@ -144,6 +150,7 @@ class JobPlexSync {
? MediaStatus.AVAILABLE ? MediaStatus.AVAILABLE
: MediaStatus.UNKNOWN; : MediaStatus.UNKNOWN;
newMedia.mediaType = MediaType.MOVIE; newMedia.mediaType = MediaType.MOVIE;
newMedia.mediaAddedAt = new Date(plexitem.addedAt * 1000);
await mediaRepository.save(newMedia); await mediaRepository.save(newMedia);
this.log(`Saved ${plexitem.title}`); this.log(`Saved ${plexitem.title}`);
} }
@@ -208,6 +215,7 @@ class JobPlexSync {
existing.status !== MediaStatus.AVAILABLE existing.status !== MediaStatus.AVAILABLE
) { ) {
existing.status = MediaStatus.AVAILABLE; existing.status = MediaStatus.AVAILABLE;
existing.mediaAddedAt = new Date(plexitem.addedAt * 1000);
changedExisting = true; changedExisting = true;
} }
@@ -220,6 +228,11 @@ class JobPlexSync {
changedExisting = true; changedExisting = true;
} }
if (!existing.mediaAddedAt && !changedExisting) {
existing.mediaAddedAt = new Date(plexitem.addedAt * 1000);
changedExisting = true;
}
if (changedExisting) { if (changedExisting) {
await mediaRepository.save(existing); await mediaRepository.save(existing);
this.log( this.log(
@@ -240,6 +253,7 @@ class JobPlexSync {
const newMedia = new Media(); const newMedia = new Media();
newMedia.imdbId = tmdbMovie.external_ids.imdb_id; newMedia.imdbId = tmdbMovie.external_ids.imdb_id;
newMedia.tmdbId = tmdbMovie.id; newMedia.tmdbId = tmdbMovie.id;
newMedia.mediaAddedAt = new Date(plexitem.addedAt * 1000);
newMedia.status = newMedia.status =
hasOtherResolution || (!this.enable4kMovie && has4k) hasOtherResolution || (!this.enable4kMovie && has4k)
? MediaStatus.AVAILABLE ? MediaStatus.AVAILABLE
@@ -266,10 +280,7 @@ class JobPlexSync {
); );
if (episodes) { if (episodes) {
for (const episode of episodes) { for (const episode of episodes) {
const special = await animeList.getSpecialEpisode( const special = animeList.getSpecialEpisode(tvdbId, episode.index);
tvdbId,
episode.index
);
if (special) { if (special) {
if (special.tmdbId) { if (special.tmdbId) {
await this.processMovieWithId(episode, undefined, special.tmdbId); await this.processMovieWithId(episode, undefined, special.tmdbId);
@@ -519,6 +530,7 @@ class JobPlexSync {
'debug' 'debug'
); );
media.lastSeasonChange = new Date(); media.lastSeasonChange = new Date();
media.mediaAddedAt = new Date(plexitem.addedAt * 1000);
} }
if (new4kSeasonAvailable > current4kSeasonAvailable) { if (new4kSeasonAvailable > current4kSeasonAvailable) {
@@ -531,6 +543,10 @@ class JobPlexSync {
media.lastSeasonChange = new Date(); media.lastSeasonChange = new Date();
} }
if (!media.mediaAddedAt) {
media.mediaAddedAt = new Date(plexitem.addedAt * 1000);
}
media.status = isAllStandardSeasons media.status = isAllStandardSeasons
? MediaStatus.AVAILABLE ? MediaStatus.AVAILABLE
: media.seasons.some( : media.seasons.some(
@@ -553,6 +569,7 @@ class JobPlexSync {
seasons: newSeasons, seasons: newSeasons,
tmdbId: tvShow.id, tmdbId: tvShow.id,
tvdbId: tvShow.external_ids.tvdb_id, tvdbId: tvShow.external_ids.tvdb_id,
mediaAddedAt: new Date(plexitem.addedAt * 1000),
status: isAllStandardSeasons status: isAllStandardSeasons
? MediaStatus.AVAILABLE ? MediaStatus.AVAILABLE
: newSeasons.some( : newSeasons.some(

View File

@@ -0,0 +1,52 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddMediaAddedFieldToMedia1610522845513
implements MigrationInterface {
name = 'AddMediaAddedFieldToMedia1610522845513';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_7ff2d11f6a83cb52386eaebe74"`);
await queryRunner.query(`DROP INDEX "IDX_41a289eb1fa489c1bc6f38d9c3"`);
await queryRunner.query(`DROP INDEX "IDX_7157aad07c73f6a6ae3bbd5ef5"`);
await queryRunner.query(
`CREATE TABLE "temporary_media" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "mediaType" varchar NOT NULL, "tmdbId" integer NOT NULL, "tvdbId" integer, "imdbId" varchar, "status" integer NOT NULL DEFAULT (1), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "lastSeasonChange" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "status4k" integer NOT NULL DEFAULT (1), "mediaAddedAt" datetime, CONSTRAINT "UQ_41a289eb1fa489c1bc6f38d9c3c" UNIQUE ("tvdbId"))`
);
await queryRunner.query(
`INSERT INTO "temporary_media"("id", "mediaType", "tmdbId", "tvdbId", "imdbId", "status", "createdAt", "updatedAt", "lastSeasonChange", "status4k") SELECT "id", "mediaType", "tmdbId", "tvdbId", "imdbId", "status", "createdAt", "updatedAt", "lastSeasonChange", "status4k" FROM "media"`
);
await queryRunner.query(`DROP TABLE "media"`);
await queryRunner.query(`ALTER TABLE "temporary_media" RENAME TO "media"`);
await queryRunner.query(
`CREATE INDEX "IDX_7ff2d11f6a83cb52386eaebe74" ON "media" ("imdbId") `
);
await queryRunner.query(
`CREATE INDEX "IDX_41a289eb1fa489c1bc6f38d9c3" ON "media" ("tvdbId") `
);
await queryRunner.query(
`CREATE INDEX "IDX_7157aad07c73f6a6ae3bbd5ef5" ON "media" ("tmdbId") `
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_7157aad07c73f6a6ae3bbd5ef5"`);
await queryRunner.query(`DROP INDEX "IDX_41a289eb1fa489c1bc6f38d9c3"`);
await queryRunner.query(`DROP INDEX "IDX_7ff2d11f6a83cb52386eaebe74"`);
await queryRunner.query(`ALTER TABLE "media" RENAME TO "temporary_media"`);
await queryRunner.query(
`CREATE TABLE "media" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "mediaType" varchar NOT NULL, "tmdbId" integer NOT NULL, "tvdbId" integer, "imdbId" varchar, "status" integer NOT NULL DEFAULT (1), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "lastSeasonChange" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "status4k" integer NOT NULL DEFAULT (1), CONSTRAINT "UQ_41a289eb1fa489c1bc6f38d9c3c" UNIQUE ("tvdbId"))`
);
await queryRunner.query(
`INSERT INTO "media"("id", "mediaType", "tmdbId", "tvdbId", "imdbId", "status", "createdAt", "updatedAt", "lastSeasonChange", "status4k") SELECT "id", "mediaType", "tmdbId", "tvdbId", "imdbId", "status", "createdAt", "updatedAt", "lastSeasonChange", "status4k" FROM "temporary_media"`
);
await queryRunner.query(`DROP TABLE "temporary_media"`);
await queryRunner.query(
`CREATE INDEX "IDX_7157aad07c73f6a6ae3bbd5ef5" ON "media" ("tmdbId") `
);
await queryRunner.query(
`CREATE INDEX "IDX_41a289eb1fa489c1bc6f38d9c3" ON "media" ("tvdbId") `
);
await queryRunner.query(
`CREATE INDEX "IDX_7ff2d11f6a83cb52386eaebe74" ON "media" ("imdbId") `
);
}
}

View File

@@ -47,6 +47,10 @@ mediaRoutes.get('/', async (req, res, next) => {
updatedAt: 'DESC', updatedAt: 'DESC',
}; };
break; break;
case 'mediaAdded':
sortFilter = {
mediaAddedAt: 'DESC',
};
} }
try { try {

View File

@@ -69,7 +69,7 @@ const Discover: React.FC = () => {
); );
const { data: media, error: mediaError } = useSWR<MediaResultsResponse>( const { data: media, error: mediaError } = useSWR<MediaResultsResponse>(
'/api/v1/media?filter=available&take=20&sort=modified' '/api/v1/media?filter=available&take=20&sort=mediaAdded'
); );
const { const {