diff --git a/src/node/consumer/src/lib/interfaces/metadata_service.ts b/src/node/consumer/src/lib/interfaces/metadata_service.ts index 71a1a2a..e7abec0 100644 --- a/src/node/consumer/src/lib/interfaces/metadata_service.ts +++ b/src/node/consumer/src/lib/interfaces/metadata_service.ts @@ -2,7 +2,7 @@ import {IMetaDataQuery} from "./metadata_query"; import {IMetadataResponse} from "./metadata_response"; export interface IMetadataService { - getKitsuId(info: IMetaDataQuery): Promise; + getKitsuId(info: IMetaDataQuery): Promise; getImdbId(info: IMetaDataQuery): Promise; diff --git a/src/node/consumer/src/lib/interfaces/parsed_torrent.ts b/src/node/consumer/src/lib/interfaces/parsed_torrent.ts index 8e227ef..f61cfeb 100644 --- a/src/node/consumer/src/lib/interfaces/parsed_torrent.ts +++ b/src/node/consumer/src/lib/interfaces/parsed_torrent.ts @@ -6,7 +6,7 @@ export interface IParsedTorrent extends IParseTorrentTitleResult { size?: number; isPack?: boolean; imdbId?: string | number; - kitsuId?: string | number; + kitsuId?: number; trackers?: string; provider?: string | null; infoHash: string | null; diff --git a/src/node/consumer/src/lib/services/torrent_download_service.ts b/src/node/consumer/src/lib/services/torrent_download_service.ts index e265fba..6201940 100644 --- a/src/node/consumer/src/lib/services/torrent_download_service.ts +++ b/src/node/consumer/src/lib/services/torrent_download_service.ts @@ -132,12 +132,13 @@ export class TorrentDownloadService implements ITorrentDownloadService { title: file.name, size: file.length, fileIndex: file.fileIndex || 0, + path: file.path, infoHash: torrent.infoHash, imdbId: torrent.imdbId.toString(), imdbSeason: torrent.season || 0, imdbEpisode: torrent.episode || 0, kitsuId: parseInt(torrent.kitsuId?.toString()) || 0, - kitsuEpisode: torrent.episode || 0 + kitsuEpisode: torrent.episode || 0, }; return {...videoFile, ...parse(file.name)}; diff --git a/src/node/consumer/src/lib/services/torrent_entries_service.ts b/src/node/consumer/src/lib/services/torrent_entries_service.ts index cc0548f..93355b6 100644 --- a/src/node/consumer/src/lib/services/torrent_entries_service.ts +++ b/src/node/consumer/src/lib/services/torrent_entries_service.ts @@ -4,7 +4,7 @@ import {TorrentType} from '../enums/torrent_types'; import {ITorrentFileCollection} from "../interfaces/torrent_file_collection"; import {Torrent} from "../../repository/models/torrent"; import {PromiseHelpers} from '../helpers/promises_helpers'; -import {ITorrentAttributes} from "../../repository/interfaces/torrent_attributes"; +import {ITorrentAttributes, ITorrentCreationAttributes} from "../../repository/interfaces/torrent_attributes"; import {File} from "../../repository/models/file"; import {Subtitle} from "../../repository/models/subtitle"; import {ITorrentEntriesService} from "../interfaces/torrent_entries_service"; @@ -15,6 +15,8 @@ import {ILoggingService} from "../interfaces/logging_service"; import {ITorrentFileService} from "../interfaces/torrent_file_service"; import {ITorrentSubtitleService} from "../interfaces/torrent_subtitle_service"; import {IDatabaseRepository} from "../../repository/interfaces/database_repository"; +import {IIngestedTorrentCreationAttributes} from "../../repository/interfaces/ingested_torrent_attributes"; +import {IFileCreationAttributes} from "../../repository/interfaces/file_attributes"; @injectable() export class TorrentEntriesService implements ITorrentEntriesService { @@ -62,8 +64,8 @@ export class TorrentEntriesService implements ITorrentEntriesService { year: titleInfo.year, season: titleInfo.season, }; - torrent.kitsuId = await this.metadataService.getKitsuId(kitsuQuery) - .catch(() => undefined); + + await this.assignKitsuId(kitsuQuery, torrent); } if (!torrent.imdbId && !torrent.kitsuId && !this.fileService.isPackTorrent(torrent)) { @@ -84,20 +86,38 @@ export class TorrentEntriesService implements ITorrentEntriesService { return; } - const newTorrent: Torrent = Torrent.build({ + const newTorrent: ITorrentCreationAttributes = ({ ...torrent, contents: fileCollection.contents, subtitles: fileCollection.subtitles }); - + return this.repository.createTorrent(newTorrent) .then(() => PromiseHelpers.sequence(fileCollection.videos.map(video => () => { - const newVideo = File.build(video); + const newVideo: IFileCreationAttributes = {...video, infoHash: video.infoHash, title: video.title}; + if (!newVideo.kitsuId) { + newVideo.kitsuId = 0; + } return this.repository.createFile(newVideo) }))) .then(() => this.logger.info(`Created ${torrent.provider} entry for [${torrent.infoHash}] ${torrent.title}`)); }; + private assignKitsuId = async (kitsuQuery: { year: number | string; season: number; title: string }, torrent: IParsedTorrent) => { + await this.metadataService.getKitsuId(kitsuQuery) + .then((result: number | Error) => { + if (typeof result === 'number') { + torrent.kitsuId = result; + } else { + torrent.kitsuId = 0; + } + }) + .catch((error: Error) => { + this.logger.debug(`Failed getting kitsuId for ${torrent.title}`, error.message); + torrent.kitsuId = 0; + }); + }; + public createSkipTorrentEntry = async (torrent: Torrent) => this.repository.createSkipTorrent(torrent); public getStoredTorrentEntry = async (torrent: Torrent) => this.repository.getSkipTorrent(torrent.infoHash) @@ -144,7 +164,7 @@ export class TorrentEntriesService implements ITorrentEntriesService { } const notOpenedVideo = storedVideos.length === 1 && !Number.isInteger(storedVideos[0].fileIndex); const imdbId: string | undefined = PromiseHelpers.mostCommonValue(storedVideos.map(stored => stored.imdbId)); - const kitsuId: number | undefined = PromiseHelpers.mostCommonValue(storedVideos.map(stored => stored.kitsuId)); + const kitsuId: number = PromiseHelpers.mostCommonValue(storedVideos.map(stored => stored.kitsuId || 0)); const fileCollection: ITorrentFileCollection = await this.fileService.parseTorrentFiles(torrent) .then(torrentContents => notOpenedVideo ? torrentContents : {...torrentContents, videos: storedVideos}) @@ -187,7 +207,7 @@ export class TorrentEntriesService implements ITorrentEntriesService { return Promise.resolve(); }) .then(() => PromiseHelpers.sequence(fileCollection.videos.map(video => () => { - const newVideo = File.build(video); + const newVideo: IFileCreationAttributes = {...video, infoHash: video.infoHash, title: video.title}; return this.repository.createFile(newVideo) }))) .then(() => this.logger.info(`Created contents for ${torrent.provider} [${torrent.infoHash}] ${torrent.title}`)) @@ -209,8 +229,8 @@ export class TorrentEntriesService implements ITorrentEntriesService { private assignMetaIds = (fileCollection: ITorrentFileCollection, imdbId: string, kitsuId: number): ITorrentFileCollection => { if (fileCollection.videos && fileCollection.videos.length) { fileCollection.videos.forEach(video => { - video.imdbId = imdbId; - video.kitsuId = kitsuId; + video.imdbId = imdbId || ''; + video.kitsuId = kitsuId || 0; }); } diff --git a/src/node/consumer/src/lib/services/torrent_file_service.ts b/src/node/consumer/src/lib/services/torrent_file_service.ts index 7990cd8..d98f128 100644 --- a/src/node/consumer/src/lib/services/torrent_file_service.ts +++ b/src/node/consumer/src/lib/services/torrent_file_service.ts @@ -191,7 +191,7 @@ export class TorrentFileService implements ITorrentFileService { episode: file.episodes && file.episodes[index], kitsuEpisode: file.episodes && file.episodes[index], episodes: file.episodes, - kitsuId: parseInt(file.kitsuId.toString() || torrent.kitsuId.toString()), + kitsuId: parseInt(file.kitsuId.toString() || torrent.kitsuId.toString()) || 0, }))) }; @@ -222,7 +222,7 @@ export class TorrentFileService implements ITorrentFileService { title: file.path || file.title, size: file.size, imdbId: imdbId, - kitsuId: parseInt(kitsuId), + kitsuId: parseInt(kitsuId) || 0, episodes: undefined, imdbSeason: undefined, imdbEpisode: undefined, @@ -239,7 +239,7 @@ export class TorrentFileService implements ITorrentFileService { title: file.path || file.title, size: file.size, imdbId: metadata.imdbId.toString() || imdbId, - kitsuId: parseInt(metadata.kitsuId.toString() || kitsuId), + kitsuId: parseInt(metadata.kitsuId.toString() || kitsuId) || 0, imdbSeason: episodeVideo && metadata.imdbId ? episodeVideo.season : undefined, imdbEpisode: episodeVideo && metadata.imdbId | metadata.kitsuId ? episodeVideo.episode || episodeVideo.episode : undefined, kitsuEpisode: episodeVideo && metadata.imdbId | metadata.kitsuId ? episodeVideo.episode || episodeVideo.episode : undefined, @@ -474,7 +474,7 @@ export class TorrentFileService implements ITorrentFileService { if (seriesMapping[file.season]) { const seasonMapping = seriesMapping[file.season]; file.imdbId = metadata.imdbId.toString(); - file.kitsuId = seasonMapping[file.episodes[0]] && seasonMapping[file.episodes[0]].kitsuId; + file.kitsuId = seasonMapping[file.episodes[0]] && seasonMapping[file.episodes[0]].kitsuId || 0; file.episodes = file.episodes.map(ep => seasonMapping[ep] && seasonMapping[ep].kitsuEpisode); } else if (seriesMapping[file.season - 1]) { // sometimes a second season might be a continuation of the previous season @@ -492,7 +492,7 @@ export class TorrentFileService implements ITorrentFileService { file.imdbId = metadata.imdbId.toString(); file.season = file.season - 1; file.episodes = file.episodes.map(ep => isAbsoluteOrder ? ep : ep + skippedCount); - file.kitsuId = seasonMapping[file.episodes[0]].kitsuId; + file.kitsuId = seasonMapping[file.episodes[0]].kitsuId || 0; file.episodes = file.episodes.map(ep => seasonMapping[ep] && seasonMapping[ep].kitsuEpisode); } } else if (Object.values(seriesMapping).length === 1 && seriesMapping[1]) { @@ -500,7 +500,7 @@ export class TorrentFileService implements ITorrentFileService { const seasonMapping = seriesMapping[1]; file.imdbId = metadata.imdbId.toString(); file.season = 1; - file.kitsuId = seasonMapping[file.episodes[0]].kitsuId; + file.kitsuId = seasonMapping[file.episodes[0]].kitsuId || 0; file.episodes = file.episodes.map(ep => seasonMapping[ep] && seasonMapping[ep].kitsuEpisode); } }); diff --git a/src/node/consumer/src/repository/database_repository.ts b/src/node/consumer/src/repository/database_repository.ts index c3ed512..2d1dfff 100644 --- a/src/node/consumer/src/repository/database_repository.ts +++ b/src/node/consumer/src/repository/database_repository.ts @@ -10,13 +10,15 @@ import {IngestedTorrent} from "./models/ingestedTorrent"; import {Subtitle} from "./models/subtitle"; import {Content} from "./models/content"; import {SkipTorrent} from "./models/skipTorrent"; -import {IFileAttributes} from "./interfaces/file_attributes"; -import {ITorrentAttributes} from "./interfaces/torrent_attributes"; +import {IFileAttributes, IFileCreationAttributes} from "./interfaces/file_attributes"; +import {ITorrentAttributes, ITorrentCreationAttributes} from "./interfaces/torrent_attributes"; import {IngestedPage} from "./models/ingestedPage"; import {ILoggingService} from "../lib/interfaces/logging_service"; import {IocTypes} from "../lib/models/ioc_types"; import {inject, injectable} from "inversify"; import {IDatabaseRepository} from "./interfaces/database_repository"; +import {IContentCreationAttributes} from "./interfaces/content_attributes"; +import {ISubtitleCreationAttributes} from "./interfaces/subtitle_attributes"; @injectable() export class DatabaseRepository implements IDatabaseRepository { @@ -112,7 +114,7 @@ export class DatabaseRepository implements IDatabaseRepository { order: literal('random()') }); - public createTorrent = async (torrent: Torrent): Promise => { + public createTorrent = async (torrent: ITorrentCreationAttributes): Promise => { try { await Torrent.upsert(torrent); await this.createContents(torrent.infoHash, torrent.contents); @@ -136,22 +138,28 @@ export class DatabaseRepository implements IDatabaseRepository { public deleteTorrent = async (infoHash: string): Promise => await Torrent.destroy({where: {infoHash: infoHash}}); - public createFile = async (file: File): Promise => { - if (file.id) { - if (file.dataValues) { - await file.save(); + public createFile = async (file: IFileCreationAttributes): Promise => { + try { + const operatingFile = File.build(file); + if (operatingFile.id) { + if (operatingFile.dataValues) { + await operatingFile.save(); + } else { + await File.upsert(operatingFile); + } + await this.upsertSubtitles(operatingFile, operatingFile.subtitles); } else { - await File.upsert(file); + if (operatingFile.subtitles && operatingFile.subtitles.length) { + operatingFile.subtitles = operatingFile.subtitles.map(subtitle => { + subtitle.title = subtitle.path; + return subtitle; + }); + } + await File.create(file, {include: [Subtitle], ignoreDuplicates: true}); } - await this.upsertSubtitles(file, file.subtitles); - } else { - if (file.subtitles && file.subtitles.length) { - file.subtitles = file.subtitles.map(subtitle => { - subtitle.title = subtitle.path; - return subtitle; - }); - } - await File.create(file, {include: [Subtitle], ignoreDuplicates: true}); + } catch (error) { + this.logger.error(`Failed to create file: ${file.infoHash}`); + this.logger.debug(error); } }; @@ -161,7 +169,7 @@ export class DatabaseRepository implements IDatabaseRepository { public deleteFile = async (id: number): Promise => File.destroy({where: {id: id}}); - public createSubtitles = async (infoHash: string, subtitles: Subtitle[]): Promise[]> => { + public createSubtitles = async (infoHash: string, subtitles: ISubtitleCreationAttributes[]): Promise[]> => { if (subtitles && subtitles.length) { return Subtitle.bulkCreate(subtitles.map(subtitle => ({infoHash, title: subtitle.path, ...subtitle}))); } @@ -191,7 +199,7 @@ export class DatabaseRepository implements IDatabaseRepository { public getUnassignedSubtitles = async (): Promise => Subtitle.findAll({where: {fileId: null}}); - public createContents = async (infoHash: string, contents: Content[]): Promise => { + public createContents = async (infoHash: string, contents: IContentCreationAttributes[]): Promise => { if (contents && contents.length) { await Content.bulkCreate(contents.map(content => ({infoHash, ...content})), {ignoreDuplicates: true}); await Torrent.update({opened: true}, {where: {infoHash: infoHash}, silent: true}); @@ -208,7 +216,7 @@ export class DatabaseRepository implements IDatabaseRepository { return result.dataValues as SkipTorrent; }; - public createSkipTorrent = async (torrent: Torrent): Promise<[SkipTorrent, boolean]> => SkipTorrent.upsert({infoHash: torrent.infoHash}); + public createSkipTorrent = async (torrent: ITorrentCreationAttributes): Promise<[SkipTorrent, boolean]> => SkipTorrent.upsert({infoHash: torrent.infoHash}); private createDatabase = (): Sequelize => { const newDatabase = new Sequelize( diff --git a/src/node/consumer/src/repository/interfaces/database_repository.ts b/src/node/consumer/src/repository/interfaces/database_repository.ts index 0a3ce38..a0d1d43 100644 --- a/src/node/consumer/src/repository/interfaces/database_repository.ts +++ b/src/node/consumer/src/repository/interfaces/database_repository.ts @@ -1,13 +1,15 @@ import {Provider} from "../models/provider"; import {WhereOptions} from "sequelize"; -import {ITorrentAttributes} from "./torrent_attributes"; +import {ITorrentAttributes, ITorrentCreationAttributes} from "./torrent_attributes"; import {Torrent} from "../models/torrent"; -import {IFileAttributes} from "./file_attributes"; +import {IFileAttributes, IFileCreationAttributes} from "./file_attributes"; import {File} from "../models/file"; import {Subtitle} from "../models/subtitle"; import {Model} from "sequelize-typescript"; import {Content} from "../models/content"; import {SkipTorrent} from "../models/skipTorrent"; +import {ISubtitleCreationAttributes} from "./subtitle_attributes"; +import {IContentCreationAttributes} from "./content_attributes"; export interface IDatabaseRepository { connect(): Promise; @@ -30,13 +32,13 @@ export interface IDatabaseRepository { getNoContentsTorrents(): Promise; - createTorrent(torrent: Torrent): Promise; + createTorrent(torrent: ITorrentCreationAttributes): Promise; setTorrentSeeders(torrent: ITorrentAttributes, seeders: number): Promise<[number]>; deleteTorrent(infoHash: string): Promise; - createFile(file: File): Promise; + createFile(file: IFileCreationAttributes): Promise; getFiles(infoHash: string): Promise; @@ -44,7 +46,7 @@ export interface IDatabaseRepository { deleteFile(id: number): Promise; - createSubtitles(infoHash: string, subtitles: Subtitle[]): Promise[]>; + createSubtitles(infoHash: string, subtitles: ISubtitleCreationAttributes[]): Promise[]>; upsertSubtitles(file: File, subtitles: Subtitle[]): Promise; @@ -52,11 +54,11 @@ export interface IDatabaseRepository { getUnassignedSubtitles(): Promise; - createContents(infoHash: string, contents: Content[]): Promise; + createContents(infoHash: string, contents: IContentCreationAttributes[]): Promise; getContents(infoHash: string): Promise; getSkipTorrent(infoHash: string): Promise; - createSkipTorrent(torrent: Torrent): Promise<[SkipTorrent, boolean]>; + createSkipTorrent(torrent: ITorrentCreationAttributes): Promise<[SkipTorrent, boolean]>; } \ No newline at end of file