Browse Source

Add ticket manager and ticket creation to manager

dev
David Ludwig 4 years ago
parent
commit
10e7960459
10 changed files with 273 additions and 53 deletions
  1. +1
    -0
      api/manager/package.json
  2. +50
    -12
      api/manager/src/IpcClient.ts
  3. +5
    -0
      api/manager/src/schema.ts
  4. +2
    -0
      services/manager/package.json
  5. +68
    -34
      services/manager/src/services/IpcInterface.ts
  6. +49
    -0
      services/manager/src/services/TicketManager/MovieTicketMap.ts
  7. +78
    -0
      services/manager/src/services/TicketManager/TicketManager.ts
  8. +3
    -0
      services/manager/src/services/TicketManager/index.ts
  9. +7
    -0
      services/manager/src/services/TicketManager/schema.ts
  10. +10
    -7
      services/manager/src/services/index.ts

+ 1
- 0
api/manager/package.json View File

@ -9,6 +9,7 @@
"clean": "rimraf ./dist"
},
"dependencies": {
"@autoplex-api/search": "^0.0.0",
"@autoplex/ipc": "^0.0.0",
"@autoplex/microservice": "^0.0.0"
}


+ 50
- 12
api/manager/src/IpcClient.ts View File

@ -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<M extends Microservice = Microservice> extends IpcClientService<M>
{
/**
@ -14,25 +28,49 @@ export class IpcClient<M extends Microservice = Microservice> 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<number>(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);
}
}

+ 5
- 0
api/manager/src/schema.ts View File

@ -1,3 +1,8 @@
export enum ErrorType {
MovieTicketConflict = "movie_ticket_conflict",
MovieNotFound = "movie_not_found"
}
export interface ITicketState {
progress: number|null
}

+ 2
- 0
services/manager/package.json View File

@ -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",


+ 68
- 34
services/manager/src/services/IpcInterface.ts View File

@ -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<TorrentManager>("Torrent Manager");
this.supervisor = this.app.service<Supervisor>("Supervisor");
this.ticketManager = this.app.service<TicketManager>("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>("Supervisor").onMovieTicketNeedsTorrent(movie);
}
// protected async onMovieTicketAdded(ticketId: number) {
// let movie = await MovieTicket.findOne(ticketId);
// if (movie === undefined) {
// return null;
// }
// this.app.service<Supervisor>("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;
// }
}

+ 49
- 0
services/manager/src/services/TicketManager/MovieTicketMap.ts View File

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

+ 78
- 0
services/manager/src/services/TicketManager/TicketManager.ts View File

@ -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>("Plex");
this.search = app.service<Search>("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() {
}
}

+ 3
- 0
services/manager/src/services/TicketManager/index.ts View File

@ -0,0 +1,3 @@
import TicketManager from "./TicketManager";
export default TicketManager;

+ 7
- 0
services/manager/src/services/TicketManager/schema.ts View File

@ -0,0 +1,7 @@
import { MovieTicket } from "@autoplex/database";
export interface IActiveMovieTicket {
ticket: MovieTicket,
progress: number,
state: undefined
}

+ 10
- 7
services/manager/src/services/index.ts View File

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

Loading…
Cancel
Save