From 10e796045961ffcbcddd0e940e9a1a2dbe297607 Mon Sep 17 00:00:00 2001 From: David Ludwig Date: Thu, 24 Jun 2021 15:52:09 -0500 Subject: [PATCH] Add ticket manager and ticket creation to manager --- api/manager/package.json | 1 + api/manager/src/IpcClient.ts | 62 ++++++++--- api/manager/src/schema.ts | 5 + services/manager/package.json | 2 + services/manager/src/services/IpcInterface.ts | 102 ++++++++++++------ .../services/TicketManager/MovieTicketMap.ts | 49 +++++++++ .../services/TicketManager/TicketManager.ts | 78 ++++++++++++++ .../src/services/TicketManager/index.ts | 3 + .../src/services/TicketManager/schema.ts | 7 ++ services/manager/src/services/index.ts | 17 +-- 10 files changed, 273 insertions(+), 53 deletions(-) create mode 100644 services/manager/src/services/TicketManager/MovieTicketMap.ts create mode 100644 services/manager/src/services/TicketManager/TicketManager.ts create mode 100644 services/manager/src/services/TicketManager/index.ts create mode 100644 services/manager/src/services/TicketManager/schema.ts diff --git a/api/manager/package.json b/api/manager/package.json index a597bfb..c2858d2 100644 --- a/api/manager/package.json +++ b/api/manager/package.json @@ -9,6 +9,7 @@ "clean": "rimraf ./dist" }, "dependencies": { + "@autoplex-api/search": "^0.0.0", "@autoplex/ipc": "^0.0.0", "@autoplex/microservice": "^0.0.0" } diff --git a/api/manager/src/IpcClient.ts b/api/manager/src/IpcClient.ts index 9dda662..5d6238e 100644 --- a/api/manager/src/IpcClient.ts +++ b/api/manager/src/IpcClient.ts @@ -1,7 +1,21 @@ +import { IMovie } from "@autoplex-api/search"; import { IpcClientService } from "@autoplex/ipc"; import { Microservice } from "@autoplex/microservice"; import { SOCKET_PATH } from "./constants"; +/** + * Internal methods + */ +export const enum IpcMethod { + // Movie Requests + ActiveMovieRequests = "movie_requests_active", + ActiveMovieRequestsForUser = "movie_requests_active_for_user", + MapMovieRequests = "movie_requests_map", + MovieRequestStatus = "movie_requests_status", + CreateMovieRequest = "movie_request_create", + CancelMovieRequest = "movie_request_cancel" +} + export class IpcClient extends IpcClientService { /** @@ -14,25 +28,49 @@ export class IpcClient extends IpcClientS */ protected readonly SOCKET_PATH = SOCKET_PATH; - // Methods ------------------------------------------------------------------------------------- + // Movie Requests ------------------------------------------------------------------------------ + + /** + * Fetch all active movie requests + */ + public async allActiveMovieRequests() { + return await this.request(IpcMethod.ActiveMovieRequests); + } + + /** + * Fetch active movie requests for the particular user + */ + public async activeMovieRequestsForUser(userId: number) { + return await this.request(IpcMethod.ActiveMovieRequestsForUser, userId); + } + + /** + * Find movie requests for the given movie list if they exist + */ + public async mapActiveMovieRequests(movies: IMovie[]) { + return await this.request(IpcMethod.MapMovieRequests, movies); + } + + /** + * Get the status of the given movie requests + */ + public async movieRequestStatus(ticketIds: number[]) { + return await this.request(IpcMethod.MovieRequestStatus, ticketIds); + } /** - * Notify Seeker that a movie was added + * Create a movie request ticket */ - public notifyMovieRequested(ticketId: number) { - this.request("movie_ticket_added", ticketId).catch((e) => { - this.log("No response from seeker notifying added movie", e); + public async createMovieRequest(userId: number, tmdbId: number) { + return await this.request(IpcMethod.CreateMovieRequest, { + userId, tmdbId }); } /** - * Get the states of the provided ticket IDs + * Cancel a movie request ticket */ - public async getMovieTicketStates(ticketIds: number[]) { - let response = await this.request("movie_ticket_states",ticketIds); - if (response.error) { - throw new Error("Failed to get movie ticket progress"); - } - return response.data; + public async cancelMovieRequest(ticketId: number) { + return await this.request(IpcMethod.CancelMovieRequest, ticketId); } } diff --git a/api/manager/src/schema.ts b/api/manager/src/schema.ts index 28d3ff9..8f7c191 100644 --- a/api/manager/src/schema.ts +++ b/api/manager/src/schema.ts @@ -1,3 +1,8 @@ +export enum ErrorType { + MovieTicketConflict = "movie_ticket_conflict", + MovieNotFound = "movie_not_found" +} + export interface ITicketState { progress: number|null } diff --git a/services/manager/package.json b/services/manager/package.json index 64e5968..33ad21d 100644 --- a/services/manager/package.json +++ b/services/manager/package.json @@ -17,6 +17,8 @@ }, "dependencies": { "@autoplex-api/manager": "^0.0.0", + "@autoplex-api/plex": "^0.0.0", + "@autoplex-api/search": "^0.0.0", "@autoplex-api/torrent": "^0.0.0", "@autoplex-api/torrent-search": "^0.0.0", "@autoplex/database": "^0.0.0", diff --git a/services/manager/src/services/IpcInterface.ts b/services/manager/src/services/IpcInterface.ts index 44568f9..7a5ed37 100644 --- a/services/manager/src/services/IpcInterface.ts +++ b/services/manager/src/services/IpcInterface.ts @@ -1,8 +1,8 @@ -import { SOCKET_PATH } from "@autoplex-api/manager"; +import { SOCKET_PATH, IpcMethod } from "@autoplex-api/manager"; +import { IMovie } from "@autoplex-api/search"; import { IpcServerService } from "@autoplex/ipc"; -import { MovieTicket } from "@autoplex/database"; import Supervisor from "./Supervisor"; -import TorrentManager from "./TorrentManager"; +import TicketManager from "./TicketManager/TicketManager"; export default class IpcInterface extends IpcServerService { @@ -19,54 +19,88 @@ export default class IpcInterface extends IpcServerService /** * Store a reference to the torrent client IPC */ - protected torrentManager!: TorrentManager; + protected supervisor!: Supervisor; /** - * Install the the event handlers + * Store a reference to the ticket manager */ - protected override installMessageHandlers() { - this.addMessageHandler("movie_ticket_added", this.onMovieTicketAdded); - this.addMessageHandler("movie_ticket_states", this.getMovieTicketStates); - } + protected ticketManager!: TicketManager; /** * Link the required services */ public override link() { - this.torrentManager = this.app.service("Torrent Manager"); + this.supervisor = this.app.service("Supervisor"); + this.ticketManager = this.app.service("Ticket Manager"); + } + + /** + * Install the the event handlers + */ + protected override installMessageHandlers() { + this.addMessageHandler(IpcMethod.ActiveMovieRequests, this.activeMovieRequests); + this.addMessageHandler(IpcMethod.ActiveMovieRequestsForUser, this.activeMovieRequestsForUser); + this.addMessageHandler(IpcMethod.MapMovieRequests, this.mapMovieRequests); + this.addMessageHandler(IpcMethod.MovieRequestStatus, this.movieRequestStatus); + this.addMessageHandler(IpcMethod.CreateMovieRequest, this.createMovieRequest); + this.addMessageHandler(IpcMethod.CancelMovieRequest, this.cancelMovieRequest); } // Interface Methods --------------------------------------------------------------------------- + public async activeMovieRequests() { + + } + + public async activeMovieRequestsForUser() { + + } + + public async mapMovieRequests() { + + } + + public async movieRequestStatus() { + + } + + public async createMovieRequest(userId: number, tmdbId: number) { + return await this.ticketManager.createMovieTicket(userId, tmdbId); + } + + public async cancelMovieRequest() { + + } + /** * Invoked when a new Movie ticket has been created */ - protected async onMovieTicketAdded(ticketId: number) { - let movie = await MovieTicket.findOne(ticketId); - if (movie === undefined) { - return null; - } - this.app.service("Supervisor").onMovieTicketNeedsTorrent(movie); - } + // protected async onMovieTicketAdded(ticketId: number) { + // let movie = await MovieTicket.findOne(ticketId); + // if (movie === undefined) { + // return null; + // } + // this.app.service("Supervisor").onMovieTicketNeedsTorrent(movie); + // } /** * Get the states of the provided movie tickets */ - protected async getMovieTicketStates(ticketIds: number[]) { - let tickets = await MovieTicket.findByIds(ticketIds, { relations: ["torrents"] }); - let torrents = this.torrentManager.cachedTorrents; - let results: any = {}; - for (let ticket of tickets) { - let result: any = {}; - if (ticket.isCanceled) { - continue; - } - if (ticket.torrents.length > 0) { - let progressList = ticket.torrents.map(torrent => torrents[torrent.infoHash].progress); - result["progress"] = Math.max(...progressList); - } - results[ticket.id] = result; - } - return results; - } + // protected async getMovieTicketStates(ticketIds: number[]) { + // let tickets = await MovieTicket.findByIds(ticketIds, { relations: ["torrents"] }); + // let torrents = this.torrentManager.cachedTorrents; + // let results: any = {}; + // for (let ticket of tickets) { + // let result: any = {}; + // if (ticket.isCanceled) { + // continue; + // } + // if (ticket.torrents.length > 0) { + // let progressList = ticket.torrents.map(torrent => torrents[torrent.infoHash].progress); + // result["progress"] = Math.max(...progressList); + // } + // results[ticket.id] = result; + // } + // return results; + // } } diff --git a/services/manager/src/services/TicketManager/MovieTicketMap.ts b/services/manager/src/services/TicketManager/MovieTicketMap.ts new file mode 100644 index 0000000..0a30d66 --- /dev/null +++ b/services/manager/src/services/TicketManager/MovieTicketMap.ts @@ -0,0 +1,49 @@ +import { MovieTicket } from "@autoplex/database"; +import { IActiveMovieTicket } from "./schema"; + +interface ITicketMap { + [ticketId: number]: IActiveMovieTicket +} + +export default class MovieTicketMap +{ + /** + * Store a mapping from movie ticket ID to movie ticket + */ + protected tickets: ITicketMap = {}; + + /** + * Store a mapping of tickets by TMDb ID + */ + protected tmdbIdMap: { [tmdbId: string]: number } = {}; + + /** + * Add a ticket to the map + */ + public add(ticket: MovieTicket) { + this.tickets[ticket.id] = { + ticket, + progress: 0.0, + state: undefined + }; + this.tmdbIdMap[ticket.tmdbId] = ticket.id; + return this.tickets[ticket.id]; + } + + /** + * Check if the given TMDb ID exists in the map + */ + public hasMovie(tmdbId: number|string) { + return tmdbId in this.tmdbIdMap; + } + + /** + * Remove a ticket from the map + */ + public remove(ticket: number|MovieTicket) { + let ticketId = (typeof ticket === "number") ? ticket : ticket.id; + let activeTicket = this.tickets[ticketId]; + delete this.tmdbIdMap[activeTicket.ticket.tmdbId]; + delete this.tickets[ticketId]; + } +} diff --git a/services/manager/src/services/TicketManager/TicketManager.ts b/services/manager/src/services/TicketManager/TicketManager.ts new file mode 100644 index 0000000..d8ee0e0 --- /dev/null +++ b/services/manager/src/services/TicketManager/TicketManager.ts @@ -0,0 +1,78 @@ +import { IpcClient as Plex } from "@autoplex-api/plex"; +import { IMovie, IpcClient as Search } from "@autoplex-api/search"; +import { ErrorType } from "@autoplex-api/manager"; +import { MovieInfo, MovieTicket, User } from "@autoplex/database"; +import { InternalService, Microservice } from "@autoplex/microservice"; +import MovieTicketMap from "./MovieTicketMap"; + + +export default class TicketManager extends InternalService +{ + /** + * The name of the service + */ + public readonly NAME = "Ticket Manager"; + + /** + * Store all active movie tickets + */ + protected activeMovieTickets: MovieTicketMap = new MovieTicketMap(); + + /** + * Store a reference to the Plex service + */ + protected plex!: Plex; + + /** + * Store a reference to the Search service + */ + protected search!: Search; + + /** + * Link to other services + */ + public override link(app: Microservice) { + this.plex = app.service("Plex"); + this.search = app.service("Search"); + } + + /** + * Create a new movie request ticket + */ + public async createMovieTicket(userId: number, tmdbId: number) { + if (this.activeMovieTickets.hasMovie(tmdbId) || await this.plex.hasMovie(tmdbId)) { + throw new Error(ErrorType.MovieTicketConflict); + } + let movie = await this.search.movieDetails(tmdbId); + if (!movie) { + throw new Error(ErrorType.MovieNotFound); + } + let info = new MovieInfo(); + info.originalLanguage = movie.originalLanguage; + info.originalTitle = movie.originalTitle; + info.overview = movie.overview; + info.posterPath = movie.posterPath; + info.backdropPath = movie.backdropPath; + info.releaseDate = movie.releaseDate; + info.runtime = movie.runtime; + await info.save(); + + let ticket = new MovieTicket(); + ticket.tmdbId = movie.tmdbId; + ticket.imdbId = movie.imdbId; + ticket.title = movie.title; + ticket.year = movie.releaseDate ? parseInt(movie.releaseDate.slice(0, 4)) : null; + ticket.user = await User.findOneOrFail(userId); + ticket.info = info; + await ticket.save(); + + return ticket.id; + } + + /** + * Cancel an active movie request ticket + */ + public async cancelMovieTicket() { + + } +} diff --git a/services/manager/src/services/TicketManager/index.ts b/services/manager/src/services/TicketManager/index.ts new file mode 100644 index 0000000..f5b9d84 --- /dev/null +++ b/services/manager/src/services/TicketManager/index.ts @@ -0,0 +1,3 @@ +import TicketManager from "./TicketManager"; + +export default TicketManager; diff --git a/services/manager/src/services/TicketManager/schema.ts b/services/manager/src/services/TicketManager/schema.ts new file mode 100644 index 0000000..a4629a5 --- /dev/null +++ b/services/manager/src/services/TicketManager/schema.ts @@ -0,0 +1,7 @@ +import { MovieTicket } from "@autoplex/database"; + +export interface IActiveMovieTicket { + ticket: MovieTicket, + progress: number, + state: undefined +} diff --git a/services/manager/src/services/index.ts b/services/manager/src/services/index.ts index 4811c1e..77eca38 100644 --- a/services/manager/src/services/index.ts +++ b/services/manager/src/services/index.ts @@ -1,15 +1,18 @@ -export { IpcClient } from "@autoplex-api/torrent-search"; -export { DatabaseService } from "@autoplex/database"; -import IpcInterface from "./IpcInterface"; -import PostProcessor from "./PostProcessor"; -import Supervisor from "./Supervisor"; -import TorrentIpc from "./TorrentIpc"; -import TorrentManager from "./TorrentManager"; +export { IpcClient as TorrentSearch } from "@autoplex-api/torrent-search"; +export { IpcClient as Plex } from "@autoplex-api/plex"; +export { DatabaseService } from "@autoplex/database"; +import IpcInterface from "./IpcInterface"; +import PostProcessor from "./PostProcessor"; +import Supervisor from "./Supervisor"; +import TicketManager from "./TicketManager"; +import TorrentIpc from "./TorrentIpc"; +import TorrentManager from "./TorrentManager"; export { IpcInterface, PostProcessor, Supervisor, + TicketManager, TorrentIpc, TorrentManager }