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

pull/598/head
sct 4 years ago
parent a262727078
commit d688a96759

@ -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':

@ -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[];
} }

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

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

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

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

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

Loading…
Cancel
Save