diff --git a/Dockerfile b/Dockerfile index f00e051..4421bfa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,8 +2,12 @@ FROM node:14-alpine AS base WORKDIR /app RUN mkdir /var/autoplex && chown node:node -R /var/autoplex +# Install packages for compiling certain Node libraries +FROM base AS base-dev +RUN apk add build-base python3 + # An image containing necessary components for building -FROM base AS builder +FROM base-dev AS builder COPY package.json yarn.lock tsconfig.json ./ RUN rm -rf node_modules && yarn install --frozen-lockfile COPY src src @@ -12,9 +16,13 @@ COPY src src FROM builder AS build RUN yarn run build +# Install production dependencies +FROM build AS build-prod +RUN rm -rf node_modules && yarn install --production --frozen-lockfile + # An image containing the built app and production dependencies FROM base AS prod -COPY --from=build /app/build ./build +COPY --from=build-prod /app/build ./build +COPY --from=build-prod /app/node_modules ./node_modules COPY package.json yarn.lock ./ -RUN rm -rf node_modules && yarn install --production --frozen-lockfile CMD [ "yarn", "run", "start" ] diff --git a/package.json b/package.json index c07a367..034e914 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "devDependencies": { "@types/node": "^14.14.41", + "@types/node-ipc": "^9.1.3", "@types/xml2js": "^0.4.8", "nodemon": "^2.0.7", "rimraf": "^3.0.2", @@ -21,6 +22,7 @@ }, "dependencies": { "cheerio": "^1.0.0-rc.6", + "diskusage": "^1.1.3", "mysql": "^2.18.1", "node-ipc": "^9.1.4", "typeorm": "^0.2.32", diff --git a/src/Application.ts b/src/Application.ts index 4e71899..3fd7a76 100644 --- a/src/Application.ts +++ b/src/Application.ts @@ -98,7 +98,7 @@ export default class Application * Get an application service instance */ public service(serviceName: string) { - assert(serviceName in this.services); + assert(serviceName in this.services, `Could not find service: ${serviceName}`); return this.services[serviceName]; } } diff --git a/src/database/entities/MovieTicket.ts b/src/database/entities/MovieTicket.ts index 9417123..67a92f5 100644 --- a/src/database/entities/MovieTicket.ts +++ b/src/database/entities/MovieTicket.ts @@ -42,4 +42,12 @@ export class MovieTicket extends BaseEntity @OneToOne(() => MovieInfo, { nullable: true }) @JoinColumn() info!: MovieInfo | null; + + /** + * Fulfill the current ticket instance + */ + async fulfill() { + this.isFulfilled = true; + return await this.save(); + } } diff --git a/src/database/entities/MovieTorrent.ts b/src/database/entities/MovieTorrent.ts index 8743acf..615cb6b 100644 --- a/src/database/entities/MovieTorrent.ts +++ b/src/database/entities/MovieTorrent.ts @@ -10,6 +10,9 @@ export class MovieTorrent extends BaseEntity @Column() infoHash!: string; + @Column() + diskName!: string; + @ManyToOne(() => MovieTicket, ticket => ticket.torrents) movieTicket!: MovieTicket; } diff --git a/src/services/IpcInterface.ts b/src/services/IpcInterface.ts index 442ea25..1bd702d 100644 --- a/src/services/IpcInterface.ts +++ b/src/services/IpcInterface.ts @@ -1,6 +1,7 @@ -import ipc from "node-ipc"; -import type { Server } from "node-ipc"; +import { IPC } from "node-ipc"; import { Socket } from "net"; +import { mkdir } from "fs/promises"; +import { dirname } from "path"; import Service from "./Service"; import Application from "../Application"; import MovieSearch from "./MovieSearch"; @@ -10,59 +11,58 @@ import { MovieTicket } from "../database/entities"; export default class IpcInterface extends Service { /** - * Quick reference to the IPC server + * The IPC instance */ - protected server!: Server; + private __ipc: InstanceType; /** * Create a new IPC interface */ public constructor(app: Application) { super("IPC", app); - ipc.config.id = "seeker"; - ipc.config.retry = 1500; - ipc.config.silent = true; + this.__ipc = new IPC(); + this.__ipc.config.id = "seeker"; + this.__ipc.config.retry = 1500; + this.__ipc.config.silent = true; } /** * Boot the IPC interface */ public boot() { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { console.log("Serving:", process.env["IPC_SOCKET_PATH"]); - ipc.serve(process.env["IPC_SOCKET_PATH"], () => { - this.server = ipc.server; - this.installEventHandlers(this.server); + await mkdir(dirname(process.env["IPC_SOCKET_PATH"]), { recursive: true }); + this.__ipc.serve(process.env["IPC_SOCKET_PATH"], () => { + this.installEventHandlers(); resolve(); }); - ipc.server.start(); + this.__ipc.server.start(); }); } public async shutdown() { - if (this.server) { - this.server.stop(); - } + this.__ipc.server.stop(); } /** * Install the the event handlers */ - protected installEventHandlers(server: Server) { - this.addEventHandler(server, "search_movie", this.searchMovie); + protected installEventHandlers() { + this.addEventHandler("movie_ticket_added", this.onMovieTicketAdded); } /** * Handle a specific event */ - protected addEventHandler(server: Server, method: string, handle: (...args: any[]) => Promise) { - server.on(method, async (message: any, socket: Socket) => { + protected addEventHandler(method: string, handle: (...args: any[]) => Promise) { + this.__ipc.server.on(method, async (message: any, socket: Socket) => { try { let response = await handle.apply(this, [message]); - this.server.emit(socket, method, { response }); + this.__ipc.server.emit(socket, method, { response }); } catch (error) { console.log("Error:", method, error); - this.server.emit(socket, method, { + this.__ipc.server.emit(socket, method, { response: undefined, error }); @@ -75,11 +75,11 @@ export default class IpcInterface extends Service /** * Invoked when a new Movie ticket has been created */ - protected async searchMovie(ticketId: number) { + protected async onMovieTicketAdded(ticketId: number) { let movie = await MovieTicket.findOne(ticketId); if (movie === undefined) { return null; } - this.app.service("Supervisor").searchMovie(movie); + this.app.service("Supervisor").onMovieTicketNeedsTorrent(movie); } } diff --git a/src/services/MovieSearch.ts b/src/services/MovieSearch.ts index 323f712..8547218 100644 --- a/src/services/MovieSearch.ts +++ b/src/services/MovieSearch.ts @@ -5,19 +5,37 @@ import Provider, { MediaType } from "../torrents/providers/Provider"; import Torrent from "../torrents/Torrent"; import { rankTorrents } from "../torrents/ranking"; import Service from "./Service"; +import Supervisor from "./Supervisor"; export default class MovieSearch extends Service { + /** + * The queue of current movie tickts requiring torrents + */ + protected movieQueue!: MovieTicket[]; + /** * Available providers that support movies */ protected providers!: Provider[]; + /** + * Indicate if the service is currently searching for movies in the queue + */ + protected isSearchingForMovies: boolean; + + /** + * Store a reference to the supervisor service + */ + protected supervisor!: Supervisor; + /** * Create a new instance of the movie search service */ public constructor(app: Application) { super("Movie Search", app); + this.movieQueue = []; + this.isSearchingForMovies = false; } /** @@ -29,6 +47,13 @@ export default class MovieSearch extends Service .map(ProviderClass => new ProviderClass()); } + /** + * Connect to the supervisor instance when everything is booted + */ + public start() { + this.supervisor = this.app.service("Supervisor"); + } + /** * Shutdown the service */ @@ -36,10 +61,41 @@ export default class MovieSearch extends Service } + // Interface Methods --------------------------------------------------------------------------- + + /** + * Enqueue a movie to the search queue + */ + public enqueueMovie(movie: MovieTicket) { + this.movieQueue.push(movie); + this.searchMovies(); + } + + // Movie Searching ----------------------------------------------------------------------------- + + /** + * Search for movies currently in the queue + */ + protected async searchMovies() { + if (this.isSearchingForMovies) { + return; + } + this.isSearchingForMovies = true; + while (this.movieQueue.length > 0) { + let movie = this.movieQueue.splice(0, 1)[0]; + let link = await this.searchMovie(movie); + if (link === null) { + continue; + } + this.supervisor.onMovieTorrentFound(movie, link); + } + this.isSearchingForMovies = false; + } + /** * Search for a movie */ - public async searchMovie(movie: MovieTicket) { + protected async searchMovie(movie: MovieTicket) { // Search by IMDb let torrents = await this.searchImdb(movie); if (torrents.length == 0) { diff --git a/src/services/PostProcessor/PostProcessor.ts b/src/services/PostProcessor/PostProcessor.ts new file mode 100644 index 0000000..a74a5e5 --- /dev/null +++ b/src/services/PostProcessor/PostProcessor.ts @@ -0,0 +1,142 @@ +import { link, mkdir } from "fs/promises"; +import { dirname, extname } from "path"; +import Application from "../../Application"; +import { MovieTicket, MovieTorrent } from "../../database/entities"; +import Service from "../Service"; +import { ISerializedTorrent } from "../TorrentManager/TorrentClientIpc"; +import { safeTitleFileName } from "../../utils"; +import Supervisor from "../Supervisor"; + +/** + * Common video file extensions + */ +const VIDEO_FILE_EXTENSIONS = new Set( + ".mp4,.mkv,.avi,.wmv,.mov".split(",") +); + +/** + * A wrapper for a pending movie torrent + */ +interface IPendingMovieTorrent { + details: ISerializedTorrent, + diskName : string, + ticket : MovieTicket +} + +export default class PostProcessor extends Service +{ + /** + * The queue of movies to process + */ + protected pendingMovies: IPendingMovieTorrent[]; + + /** + * Indicate if movies are currently being processed + */ + protected isProcessingMovies: boolean; + + /** + * Create a new instance of the post processor + */ + public constructor(app: Application) { + super("Post Processor", app); + this.pendingMovies = []; + this.isProcessingMovies = false; + } + + /** + * Boot the post-processor service + */ + public async boot() { + + } + + /** + * Shutdown the post-processor service + */ + public async shutdown() { + + } + + // Methods ------------------------------------------------------------------------------------- + + /** + * Enqueue a completed movie for post-processing + */ + public enqueueMovie(torrent: MovieTorrent, details: ISerializedTorrent) { + this.pendingMovies.push({ticket: torrent.movieTicket, diskName: torrent.diskName, details}); + this.processMovies(); + } + + /** + * Process the enqueued movies + */ + public async processMovies() { + if (this.isProcessingMovies) { + return; + } + this.isProcessingMovies = true; + while (this.pendingMovies.length > 0) { + let movie = this.pendingMovies.splice(0, 1)[0]; + this.processMovie(movie); + } + this.isProcessingMovies = false; + } + + /** + * Process an enqueue movie + */ + protected async processMovie(pendingMovie: IPendingMovieTorrent) { + let movieFile = this.findMovieFile(pendingMovie.details); + if (!movieFile) { + this.log("Unable to find a completed movie file in the torrent:", pendingMovie.details.name); + return; + } + + // Generate the movie file's name + let movieName = safeTitleFileName(pendingMovie.ticket.title); + if (pendingMovie.ticket.year !== null) { + movieName += ` (${pendingMovie.ticket.year})`; + } + + let basePath = `/mnt/movies/${pendingMovie.diskName}`; + let from = `${basePath}/Downloads/${movieFile.path}`; + let to = `${basePath}/Media/${movieName}/${movieName}${extname(movieFile.path)}`; + await this.hardlinkFile(from, to); + + // Notify the supervisor that the movie has been processed + this.app.service("Supervisor").onMovieTorrentProcessed(pendingMovie.ticket); + } + + /** + * Locate the movie file in a torrent + */ + protected findMovieFile(details: ISerializedTorrent) { + // Find video files in the torrent + let movieFiles = details.files.filter( + file => file.selected && file.downloaded == file.size + && VIDEO_FILE_EXTENSIONS.has(extname(file.path).toLowerCase()) + ); + // Find the video file with the largest size + let maxIndex = 0; + for (let i = 0; i < movieFiles.length; i++) { + if (movieFiles[i].size > movieFiles[maxIndex].size) { + maxIndex = i; + } + } + // Return the result + if (movieFiles[maxIndex] === undefined) { + return null; + } + return movieFiles[maxIndex]; + } + + /** + * Hardlink a file to another directory + */ + protected async hardlinkFile(from: string, to: string) { + this.log("Creating hardlink:", from, "->", to); + await mkdir(dirname(to), { recursive: true }); + await link(from, to); + } +} diff --git a/src/services/PostProcessor/index.ts b/src/services/PostProcessor/index.ts new file mode 100644 index 0000000..abfbdcd --- /dev/null +++ b/src/services/PostProcessor/index.ts @@ -0,0 +1,3 @@ +import PostProcessor from "./PostProcessor"; + +export default PostProcessor; diff --git a/src/services/Supervisor.ts b/src/services/Supervisor.ts index e959d08..cc54551 100644 --- a/src/services/Supervisor.ts +++ b/src/services/Supervisor.ts @@ -1,15 +1,13 @@ import Application from "../Application"; import { MovieTicket, MovieTorrent } from "../database/entities"; import MovieSearch from "./MovieSearch"; +import PostProcessor from "./PostProcessor"; import Service from "./Service"; -import TorrentClientIpc, { TorrentClientConnectionError } from "./TorrentClientIpc"; +import TorrentManager from "./TorrentManager"; +import { ISerializedTorrent } from "./TorrentManager/TorrentClientIpc"; export default class Supervisor extends Service { - /** - * Keep a list of pending torrent links to add - */ - protected pendingTorrentsToAdd: string[]; /** * The movie search service instance @@ -19,14 +17,18 @@ export default class Supervisor extends Service /** * The torrent client IPC service instance */ - protected torrentClient!: TorrentClientIpc; + protected torrentManager!: TorrentManager; + + /** + * The post-processor service instance + */ + protected postProcessor!: PostProcessor; /** * Create a new supervisor service instance */ public constructor(app: Application) { super("Supervisor", app); - this.pendingTorrentsToAdd = []; } /** @@ -39,7 +41,8 @@ export default class Supervisor extends Service */ public start() { this.movieSearch = this.app.service("Movie Search"); - this.torrentClient = this.app.service("Torrent Client IPC"); + this.torrentManager = this.app.service("Torrent Manager"); + this.postProcessor = this.app.service("Post Processor"); this.searchMovies(); } @@ -48,6 +51,41 @@ export default class Supervisor extends Service */ public async shutdown() {} + // Request Flow -------------------------------------------------------------------------------- + + /** + * 1. A movie ticket has been added/torrent has gone stale, so + * dispatch a search task for a torrent + */ + public onMovieTicketNeedsTorrent(ticket: MovieTicket) { + this.log("A movie needs a torrent:", ticket.title); + this.movieSearch.enqueueMovie(ticket); + } + + /** + * 2. A movie torrent has been found, so add it to the client + */ + public onMovieTorrentFound(ticket: MovieTicket, link: string) { + this.log("A torrent was found for movie:", ticket.title); + this.torrentManager.enqueueMovie(ticket, link); + } + + /** + * 3. A torrent has finished downloading, so send it for post-processing + */ + public onMovieTorrentFinished(torrent: MovieTorrent, details: ISerializedTorrent) { + this.log("A torrent has finished for movie:", torrent.movieTicket.title); + this.postProcessor.enqueueMovie(torrent, details); + } + + /** + * 4. A ticket's torrent has finished post-processing, so fulfill the ticket + */ + public onMovieTorrentProcessed(ticket: MovieTicket) { + this.log("A movie torrent has been successfully processed:", ticket.title); + ticket.fulfill(); + } + // Tasks --------------------------------------------------------------------------------------- /** @@ -62,36 +100,7 @@ export default class Supervisor extends Service this.log("Skipping already satisfied ticket") continue; } - await this.searchMovie(movie); - } - } - - /** - * Search for a movie and add it to the torrent client - */ - public async searchMovie(movie: MovieTicket) { - // Search for a movie torrent - let link = await this.movieSearch.searchMovie(movie); - if (link === null) { - return false; - } - this.log("Found a torrent for:", movie.title, link); - // Send the link to the client - let infoHash: string; - try { - infoHash = await this.torrentClient.add(link); - } catch(e) { - if (e instanceof TorrentClientConnectionError) { - this.log("Failed to add torrent to client... Added to pending"); - this.pendingTorrentsToAdd.push(link); - } - return false; + this.onMovieTicketNeedsTorrent(movie); } - // Store a reference to this torrent in the database - let torrent = new MovieTorrent(); - torrent.infoHash = infoHash; - torrent.movieTicket = movie; - await torrent.save(); - return true; } } diff --git a/src/services/TorrentClientIpc.ts b/src/services/TorrentManager/TorrentClientIpc.ts similarity index 78% rename from src/services/TorrentClientIpc.ts rename to src/services/TorrentManager/TorrentClientIpc.ts index a366505..651ee36 100644 --- a/src/services/TorrentClientIpc.ts +++ b/src/services/TorrentManager/TorrentClientIpc.ts @@ -1,7 +1,7 @@ -import ipc from "node-ipc"; import { Socket } from "net"; -import Application from "../Application"; -import Service from "./Service"; +import Application from "../../Application"; +import Service from "../Service"; +import ipc = require("node-ipc"); interface IResponse { response?: any, @@ -65,6 +65,11 @@ export default class TorrentClientIpc extends Service */ private __isConnected: boolean; + /** + * IPC instance + */ + private __ipc = new ipc.IPC(); + /** * The active IPC socket */ @@ -73,11 +78,11 @@ export default class TorrentClientIpc extends Service /** * Create a new IPC client for the torrent client */ - constructor(app: Application) { - super("Torrent Client IPC", app); - ipc.config.id = "torrent_webui"; - ipc.config.retry = 1500; - ipc.config.silent = true; + constructor(name: string, app: Application) { + super(name, app); + this.__ipc.config.id = "seeker"; + this.__ipc.config.retry = 1500; + this.__ipc.config.silent = true; this.__isConnected = false; } @@ -87,8 +92,8 @@ export default class TorrentClientIpc extends Service */ public boot() { return new Promise((resolve, reject) => { - ipc.connectTo("torrent_client", process.env["TORRENT_CLIENT_IPC_SOCKET"], () => { - this.socket = ipc.of["torrent_client"]; + this.__ipc.connectTo("torrent_client", process.env["TORRENT_CLIENT_IPC_SOCKET"], () => { + this.socket = this.__ipc.of["torrent_client"]; this.installSocketEventHandlers(this.socket); this.installSocketMessageHandlers(this.socket); resolve(); @@ -100,7 +105,7 @@ export default class TorrentClientIpc extends Service * Shutdown the service */ public async shutdown() { - + this.__ipc.disconnect("torrent_client"); } /** @@ -114,6 +119,14 @@ export default class TorrentClientIpc extends Service } protected installSocketMessageHandlers(socket: Socket) { + this.installSocketMessageHandler(socket, "torrent_finished", this.onTorrentFinished); + } + + /** + * Install a socket message handler + */ + protected installSocketMessageHandler(socket: Socket, method: string, handler: (...args: any[]) => void) { + socket.on(method, (args: any[]) => handler.apply(this, args)); } // Socket Event Handlers ----------------------------------------------------------------------- @@ -140,12 +153,19 @@ export default class TorrentClientIpc extends Service this.log("IPC: Destroyed"); } + // Socket Message Handlers --------------------------------------------------------------------- + + /** + * Invoked when a torrent has finished downloading + */ + protected async onTorrentFinished(infoHash: string) {} + // Methods ------------------------------------------------------------------------------------- /** * Perform a general request to the torrent client */ - protected async request(method: string, message?: any) { + protected async request(method: string, ...message: any[]) { return new Promise((resolve, reject) => { if (!this.isConnected) { reject(new TorrentClientConnectionError("Not connected to torrent client")); @@ -169,8 +189,8 @@ export default class TorrentClientIpc extends Service * Add a torrent to the client * @param torrent Magnet URI or file buffer */ - public async add(torrent: string | Buffer) { - let response = await this.request("add", torrent); + protected async add(torrent: string | Buffer, downloadPath?: string) { + let response = await this.request("add", torrent, downloadPath); if (response.error) { throw new Error("Failed to add torrent"); } @@ -181,7 +201,7 @@ export default class TorrentClientIpc extends Service * Remove a torrent from the client * @param torrent Torrent info hash */ - public async remove(torrent: string) { + protected async remove(torrent: string) { let response = await this.request("remove", torrent); if (response.error) { throw new Error("Failed to remove torrent"); @@ -191,7 +211,7 @@ export default class TorrentClientIpc extends Service /** * Get a list of all torrents in the client */ - public async list() { + protected async list() { let response = await this.request("list"); if (response.error) { console.error(response.error); @@ -204,7 +224,7 @@ export default class TorrentClientIpc extends Service * Get full details of each of the provided torrents * @param torrentIds Array of torrent info hashes */ - public async details(...torrentIds: string[]) { + protected async details(...torrentIds: string[]) { let response = await this.request("details", torrentIds); if (response.error) { console.error(response.error); diff --git a/src/services/TorrentManager/TorrentManager.ts b/src/services/TorrentManager/TorrentManager.ts new file mode 100644 index 0000000..b6ad9f3 --- /dev/null +++ b/src/services/TorrentManager/TorrentManager.ts @@ -0,0 +1,162 @@ +import diskusage from "diskusage"; +import { readdir } from "fs/promises"; +import Application from "../../Application" +import { MovieTicket, MovieTorrent } from "../../database/entities"; +import Supervisor from "../Supervisor"; +import TorrentClientIpc, { TorrentClientConnectionError } from "./TorrentClientIpc" + + interface IPendingMovieTorrent { + link: string, + movie: MovieTicket +} + +/** + * A mapping of available disks + */ +interface IDiskMap { + movies: string[], + // tvshows: string[] +} + +export default class TorrentManager extends TorrentClientIpc +{ + /** + * The queue of movies to add to the client + */ + protected pendingMovies: IPendingMovieTorrent[]; + + /** + * Indicate if the service is currently adding movies to the torrent client + */ + protected isAddingMovies: boolean; + + /** + * Available movie disk names + */ + protected disks!: IDiskMap; + + /** + * Create a new torrent manager instance + */ + public constructor(app: Application) { + super("Torrent Manager", app); + this.pendingMovies = []; + this.isAddingMovies = false; + } + + /** + * Boot the Torrent Manager service + */ + public async boot() { + this.log("Booting the torrent manager"); + await super.boot(); + await this.loadDisks(); + } + + // Interface methods --------------------------------------------------------------------------- + + /** + * Add a movie to the queue + */ + public enqueueMovie(movie: MovieTicket, link: string) { + this.pendingMovies.push({movie, link}); + this.addMovies(); + } + + // Volume Management --------------------------------------------------------------------------- + + /** + * Load available storage disks + */ + public async loadDisks() { + this.disks = { + movies: await readdir("/mnt/movies"), + // tvshows: await readdir("/mnt/tvshows") + } + } + + /** + * Get the disk with the most space available + */ + public async findMostEmptyDisk(diskType: "movies") { + let diskSizes = await Promise.all(this.disks[diskType].map( + async diskName => (await diskusage.check(`/mnt/${diskType}/${diskName}`)).available + )); + let minIndex = 0; + for (let i = 1; i < diskSizes.length; i++) { + if (diskSizes[i] < diskSizes[minIndex]) { + minIndex = i; + } + } + return this.disks[diskType][minIndex]; + } + + // Movie Management ---------------------------------------------------------------------------- + + /** + * Add the movies in the queue to the torrent client + */ + protected async addMovies() { + if (this.isAddingMovies || !this.isConnected || this.pendingMovies.length == 0) { + return; + } + this.isAddingMovies = true; + let diskName = await this.findMostEmptyDisk("movies"); + while (this.pendingMovies.length > 0) { + let {movie, link} = this.pendingMovies.splice(0, 1)[0]; + try { + await this.addMovie(movie, link, diskName); + } catch(e) { + this.log("Failed to add torrent to client... Added to pending"); + this.pendingMovies.push({movie, link}); + break; + } + } + this.isAddingMovies = false; + } + + /** + * Add a movie to the torrent client + */ + public async addMovie(movie: MovieTicket, link: string, diskName: string) { + try { + let infoHash = await this.add(link, `/mnt/movies/${diskName}/Downloads`); + let torrent = new MovieTorrent(); + torrent.infoHash = infoHash; + torrent.diskName = diskName; + torrent.movieTicket = movie; + await torrent.save(); + } catch(e) { + if (e instanceof TorrentClientConnectionError) { + throw e; + } + console.log("Failed download the torrent"); + return false; + } + return true; + } + + // Event Handling ------------------------------------------------------------------------------ + + /** + * Invoked when the connection to the torrent client is established/re-established + */ + protected onConnect() { + super.onConnect(); + this.addMovies(); + } + + /** + * Invoked when a torrent + */ + protected async onTorrentFinished(infoHash: string) { + let torrent = await MovieTorrent.findOne({ + where: { infoHash }, relations: ["movieTicket"] + }); + if (torrent !== undefined) { + let details = (await this.details(infoHash))[0]; + this.app.service("Supervisor").onMovieTorrentFinished(torrent, details); + } + // this.app.service(); + } +} diff --git a/src/services/TorrentManager/index.ts b/src/services/TorrentManager/index.ts new file mode 100644 index 0000000..b0931ce --- /dev/null +++ b/src/services/TorrentManager/index.ts @@ -0,0 +1,3 @@ +import TorrentManager from "./TorrentManager"; + +export default TorrentManager; diff --git a/src/services/index.ts b/src/services/index.ts index 0eede1e..95653c4 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,13 +1,15 @@ import Database from "./Database"; import IpcInterface from "./IpcInterface"; import MovieSearch from "./MovieSearch"; +import PostProcessor from "./PostProcessor"; import Supervisor from "./Supervisor"; -import TorrentClientIpc from "./TorrentClientIpc"; +import TorrentManager from "./TorrentManager"; export default { Database, IpcInterface, MovieSearch, + PostProcessor, Supervisor, - TorrentClientIpc, + TorrentManager, } diff --git a/src/torrents/ranking.ts b/src/torrents/ranking.ts index 488c305..2696e4f 100644 --- a/src/torrents/ranking.ts +++ b/src/torrents/ranking.ts @@ -18,7 +18,7 @@ function selectPreferredTorrents(torrent: Torrent) { return false; } if (torrent.metadata.resolution == Resolution.HD4k) { - return torrent.size != null && torrent.size < 15*1024*1024*1024; // 15GB + return torrent.size != null && torrent.size < 10*1024*1024*1024; // 10GB } return true; } diff --git a/src/typings/node-ipc/index.d.ts b/src/typings/node-ipc/index.d.ts index 276368b..c4040ab 100644 --- a/src/typings/node-ipc/index.d.ts +++ b/src/typings/node-ipc/index.d.ts @@ -1,350 +1,342 @@ -/// - -declare module "node-ipc" { - - import { Socket } from "net"; - - declare const NodeIPC: NodeIPC.NodeIPC; - - declare namespace NodeIPC { - interface NodeIPC extends IPC - {} - - interface IPC { - /** - * Set these variables in the ipc.config scope to overwrite or set default values - */ - config: Config; - /** - * https://www.npmjs.com/package/node-ipc#log - */ - log(...args: any[]): void; - /** - * https://www.npmjs.com/package/node-ipc#connectto - * Used for connecting as a client to local Unix Sockets and Windows Sockets. - * This is the fastest way for processes on the same machine to communicate - * because it bypasses the network card which TCP and UDP must both use. - * @param id is the string id of the socket being connected to. - * The socket with this id is added to the ipc.of object when created. - * @param path is the path of the Unix Domain Socket File, if the System is Windows, - * this will automatically be converted to an appropriate pipe with the same information as the Unix Domain Socket File. - * If not set this will default to ipc.config.socketRoot+ipc.config.appspace+id - * @param callback this is the function to execute when the socket has been created - */ - connectTo(id: string, path?: string, callback?: () => void): void; - /** - * https://www.npmjs.com/package/node-ipc#connectto - * Used for connecting as a client to local Unix Sockets and Windows Sockets. - * This is the fastest way for processes on the same machine to communicate - * because it bypasses the network card which TCP and UDP must both use. - * @param id is the string id of the socket being connected to. - * The socket with this id is added to the ipc.of object when created. - * @param callback this is the function to execute when the socket has been created - */ - connectTo(id: string, callback?: () => void): void; - /** - * https://www.npmjs.com/package/node-ipc#connecttonet - * Used to connect as a client to a TCP or TLS socket via the network card. - * This can be local or remote, if local, it is recommended that you use the Unix - * and Windows Socket Implementaion of connectTo instead as it is much faster since it avoids the network card altogether. - * For TLS and SSL Sockets see the node-ipc TLS and SSL docs. - * They have a few additional requirements, and things to know about and so have their own doc. - * @param id is the string id of the socket being connected to. For TCP & TLS sockets, - * this id is added to the ipc.of object when the socket is created with a reference to the socket - * @param host is the host on which the TCP or TLS socket resides. - * This will default to ipc.config.networkHost if not specified - * @param port the port on which the TCP or TLS socket resides - * @param callback this is the function to execute when the socket has been created - */ - connectToNet(id: string, host?: string, port?: number, callback?: () => void): void; - /** - * https://www.npmjs.com/package/node-ipc#connecttonet - * Used to connect as a client to a TCP or TLS socket via the network card. - * This can be local or remote, if local, it is recommended that you use the Unix - * and Windows Socket Implementaion of connectTo instead as it is much faster since it avoids the network card altogether. - * For TLS and SSL Sockets see the node-ipc TLS and SSL docs. - * They have a few additional requirements, and things to know about and so have their own doc. - * @param id is the string id of the socket being connected to. For TCP & TLS sockets, - * this id is added to the ipc.of object when the socket is created with a reference to the socket - * @param callback this is the function to execute when the socket has been created - */ - connectToNet(id: string, callback?: () => void): void; - /** - * https://www.npmjs.com/package/node-ipc#connecttonet - * Used to connect as a client to a TCP or TLS socket via the network card. - * This can be local or remote, if local, it is recommended that you use the Unix - * and Windows Socket Implementaion of connectTo instead as it is much faster since it avoids the network card altogether. - * For TLS and SSL Sockets see the node-ipc TLS and SSL docs. - * They have a few additional requirements, and things to know about and so have their own doc. - * @param id is the string id of the socket being connected to. - * For TCP & TLS sockets, this id is added to the ipc.of object when the socket is created with a reference to the socket - * @param host is the host on which the TCP or TLS socket resides. This will default to ipc.config.networkHost if not specified - * @param port the port on which the TCP or TLS socket resides - * @param callback this is the function to execute when the socket has been created - */ - connectToNet(id: string, hostOrPort: number | string, callback?: () => void): void; - /** - * https://www.npmjs.com/package/node-ipc#disconnect - * Used to disconnect a client from a Unix, Windows, TCP or TLS socket. - * The socket and its refrence will be removed from memory and the ipc.of scope. - * This can be local or remote. UDP clients do not maintain connections and so there are no Clients and this method has no value to them - * @param id is the string id of the socket from which to disconnect - */ - disconnect(id: string): void; - /** - * https://www.npmjs.com/package/node-ipc#serve - * Used to create local Unix Socket Server or Windows Socket Server to which Clients can bind. - * The server can emit events to specific Client Sockets, or broadcast events to all known Client Sockets - * @param path This is the path of the Unix Domain Socket File, if the System is Windows, - * this will automatically be converted to an appropriate pipe with the same information as the Unix Domain Socket File. - * If not set this will default to ipc.config.socketRoot+ipc.config.appspace+id - * @param callback This is a function to be called after the Server has started. - * This can also be done by binding an event to the start event like ipc.server.on('start',function(){}); - */ - serve(path: string, callback?: () => void): void; - /** - * https://www.npmjs.com/package/node-ipc#serve - * Used to create local Unix Socket Server or Windows Socket Server to which Clients can bind. - * The server can emit events to specific Client Sockets, or broadcast events to all known Client Sockets - * @param callback This is a function to be called after the Server has started. - * This can also be done by binding an event to the start event like ipc.server.on('start',function(){}); - */ - serve(callback?: () => void): void; - /** - * https://www.npmjs.com/package/node-ipc#serve - * Used to create local Unix Socket Server or Windows Socket Server to which Clients can bind. - * The server can emit events to specific Client Sockets, or broadcast events to all known Client Sockets - */ - serve(callback: null): void; - /** - * https://www.npmjs.com/package/node-ipc#servenet - * @param host If not specified this defaults to the first address in os.networkInterfaces(). - * For TCP, TLS & UDP servers this is most likely going to be 127.0.0.1 or ::1 - * @param port The port on which the TCP, UDP, or TLS Socket server will be bound, this defaults to 8000 if not specified - * @param UDPType If set this will create the server as a UDP socket. 'udp4' or 'udp6' are valid values. - * This defaults to not being set. When using udp6 make sure to specify a valid IPv6 host, like ::1 - * @param callback Function to be called when the server is created - */ - serveNet(host?: string, port?: number, UDPType?: "udp4" | "udp6", callback?: () => void): void; - /** - * https://www.npmjs.com/package/node-ipc#servenet - * @param UDPType If set this will create the server as a UDP socket. 'udp4' or 'udp6' are valid values. - * This defaults to not being set. When using udp6 make sure to specify a valid IPv6 host, like ::1 - * @param callback Function to be called when the server is created - */ - serveNet(UDPType: "udp4" | "udp6", callback?: () => void): void; - /** - * https://www.npmjs.com/package/node-ipc#servenet - * @param callback Function to be called when the server is created - * @param port The port on which the TCP, UDP, or TLS Socket server will be bound, this defaults to 8000 if not specified - */ - serveNet(callbackOrPort: EmptyCallback | number): void; - /** - * https://www.npmjs.com/package/node-ipc#servenet - * @param host If not specified this defaults to the first address in os.networkInterfaces(). - * For TCP, TLS & UDP servers this is most likely going to be 127.0.0.1 or ::1 - * @param port The port on which the TCP, UDP, or TLS Socket server will be bound, this defaults to 8000 if not specified - * @param callback Function to be called when the server is created - */ - serveNet(host: string, port: number, callback?: () => void): void; - /** - * This is where socket connection refrences will be stored when connecting to them as a client via the ipc.connectTo - * or iupc.connectToNet. They will be stored based on the ID used to create them, eg : ipc.of.mySocket - */ - of: any; - /** - * This is a refrence to the server created by ipc.serve or ipc.serveNet - */ - server: Server; - } - type EmptyCallback = () => void; - interface Client { - /** - * triggered when a JSON message is received. The event name will be the type string from your message - * and the param will be the data object from your message eg : { type:'myEvent',data:{a:1}} - */ - on(event: string, callback: (message: any, socket: Socket) => void): Client; - /** - * triggered when an error has occured - */ - on(event: "error", callback: (err: any) => void): Client; - /** - * connect - triggered when socket connected - * disconnect - triggered by client when socket has disconnected from server - * destroy - triggered when socket has been totally destroyed, no further auto retries will happen and all references are gone - */ - on(event: "connect" | "disconnect" | "destroy", callback: () => void): Client; - /** - * triggered by server when a client socket has disconnected - */ - on(event: "socket.disconnected", callback: (socket: Socket, destroyedSocketID: string) => void): Client; - /** - * triggered when ipc.config.rawBuffer is true and a message is received - */ - on(event: "data", callback: (buffer: Buffer) => void): Client; - emit(event: string, value?: any): Client; - /** - * Unbind subscribed events - */ - off(event: string, handler: any): Client; - } - interface Server extends Client { - /** - * start serving need top call serve or serveNet first to set up the server - */ - start(): void; - /** - * close the server and stop serving - */ - stop(): void; - emit(value: any): Client; - emit(event: string, value: any): Client; - emit(socket: Socket | SocketConfig, event: string, value?: any): Server; - emit(socketConfig: Socket | SocketConfig, value?: any): Server; - } - interface SocketConfig { - address?: string; - port?: number; - } - interface Config { - /** - * Default: 'app.' - * Used for Unix Socket (Unix Domain Socket) namespacing. - * If not set specifically, the Unix Domain Socket will combine the socketRoot, appspace, - * and id to form the Unix Socket Path for creation or binding. - * This is available incase you have many apps running on your system, you may have several sockets with the same id, - * but if you change the appspace, you will still have app specic unique sockets - */ - appspace: string; - /** - * Default: '/tmp/' - * The directory in which to create or bind to a Unix Socket - */ - socketRoot: string; - /** - * Default: os.hostname() - * The id of this socket or service - */ - id: string; - /** - * Default: 'localhost' - * The local or remote host on which TCP, TLS or UDP Sockets should connect - * Should resolve to 127.0.0.1 or ::1 see the table below related to this - */ - networkHost: string; - /** - * Default: 8000 - * The default port on which TCP, TLS, or UDP sockets should connect - */ - networkPort: number; - /** - * Default: 'utf8' - * the default encoding for data sent on sockets. Mostly used if rawBuffer is set to true. - * Valid values are : ascii utf8 utf16le ucs2 base64 hex - */ - encoding: "ascii" | "utf8" | "utf16le" | "ucs2" | "base64" | "hex"; +// Type definitions for node-ipc 9.1 +// Project: http://riaevangelist.github.io/node-ipc/ +// Definitions by: Arvitaly , gjurgens +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +/// +import { Socket } from "net"; +declare namespace NodeIPC { + export class IPC { + /** + * Set these variables in the ipc.config scope to overwrite or set default values + */ + config: Config; + /** + * https://www.npmjs.com/package/node-ipc#log + */ + log(...args: any[]): void; + /** + * https://www.npmjs.com/package/node-ipc#connectto + * Used for connecting as a client to local Unix Sockets and Windows Sockets. + * This is the fastest way for processes on the same machine to communicate + * because it bypasses the network card which TCP and UDP must both use. + * @param id is the string id of the socket being connected to. + * The socket with this id is added to the ipc.of object when created. + * @param path is the path of the Unix Domain Socket File, if the System is Windows, + * this will automatically be converted to an appropriate pipe with the same information as the Unix Domain Socket File. + * If not set this will default to ipc.config.socketRoot+ipc.config.appspace+id + * @param callback this is the function to execute when the socket has been created + */ + connectTo(id: string, path?: string, callback?: () => void): void; + /** + * https://www.npmjs.com/package/node-ipc#connectto + * Used for connecting as a client to local Unix Sockets and Windows Sockets. + * This is the fastest way for processes on the same machine to communicate + * because it bypasses the network card which TCP and UDP must both use. + * @param id is the string id of the socket being connected to. + * The socket with this id is added to the ipc.of object when created. + * @param callback this is the function to execute when the socket has been created + */ + connectTo(id: string, callback?: () => void): void; + /** + * https://www.npmjs.com/package/node-ipc#connecttonet + * Used to connect as a client to a TCP or TLS socket via the network card. + * This can be local or remote, if local, it is recommended that you use the Unix + * and Windows Socket Implementaion of connectTo instead as it is much faster since it avoids the network card altogether. + * For TLS and SSL Sockets see the node-ipc TLS and SSL docs. + * They have a few additional requirements, and things to know about and so have their own doc. + * @param id is the string id of the socket being connected to. For TCP & TLS sockets, + * this id is added to the ipc.of object when the socket is created with a reference to the socket + * @param host is the host on which the TCP or TLS socket resides. + * This will default to ipc.config.networkHost if not specified + * @param port the port on which the TCP or TLS socket resides + * @param callback this is the function to execute when the socket has been created + */ + connectToNet(id: string, host?: string, port?: number, callback?: () => void): void; + /** + * https://www.npmjs.com/package/node-ipc#connecttonet + * Used to connect as a client to a TCP or TLS socket via the network card. + * This can be local or remote, if local, it is recommended that you use the Unix + * and Windows Socket Implementaion of connectTo instead as it is much faster since it avoids the network card altogether. + * For TLS and SSL Sockets see the node-ipc TLS and SSL docs. + * They have a few additional requirements, and things to know about and so have their own doc. + * @param id is the string id of the socket being connected to. For TCP & TLS sockets, + * this id is added to the ipc.of object when the socket is created with a reference to the socket + * @param callback this is the function to execute when the socket has been created + */ + connectToNet(id: string, callback?: () => void): void; + /** + * https://www.npmjs.com/package/node-ipc#connecttonet + * Used to connect as a client to a TCP or TLS socket via the network card. + * This can be local or remote, if local, it is recommended that you use the Unix + * and Windows Socket Implementaion of connectTo instead as it is much faster since it avoids the network card altogether. + * For TLS and SSL Sockets see the node-ipc TLS and SSL docs. + * They have a few additional requirements, and things to know about and so have their own doc. + * @param id is the string id of the socket being connected to. + * For TCP & TLS sockets, this id is added to the ipc.of object when the socket is created with a reference to the socket + * @param host is the host on which the TCP or TLS socket resides. This will default to ipc.config.networkHost if not specified + * @param port the port on which the TCP or TLS socket resides + * @param callback this is the function to execute when the socket has been created + */ + connectToNet(id: string, hostOrPort: number | string, callback?: () => void): void; + /** + * https://www.npmjs.com/package/node-ipc#disconnect + * Used to disconnect a client from a Unix, Windows, TCP or TLS socket. + * The socket and its refrence will be removed from memory and the ipc.of scope. + * This can be local or remote. UDP clients do not maintain connections and so there are no Clients and this method has no value to them + * @param id is the string id of the socket from which to disconnect + */ + disconnect(id: string): void; + /** + * https://www.npmjs.com/package/node-ipc#serve + * Used to create local Unix Socket Server or Windows Socket Server to which Clients can bind. + * The server can emit events to specific Client Sockets, or broadcast events to all known Client Sockets + * @param path This is the path of the Unix Domain Socket File, if the System is Windows, + * this will automatically be converted to an appropriate pipe with the same information as the Unix Domain Socket File. + * If not set this will default to ipc.config.socketRoot+ipc.config.appspace+id + * @param callback This is a function to be called after the Server has started. + * This can also be done by binding an event to the start event like ipc.server.on('start',function(){}); + */ + serve(path: string, callback?: () => void): void; + /** + * https://www.npmjs.com/package/node-ipc#serve + * Used to create local Unix Socket Server or Windows Socket Server to which Clients can bind. + * The server can emit events to specific Client Sockets, or broadcast events to all known Client Sockets + * @param callback This is a function to be called after the Server has started. + * This can also be done by binding an event to the start event like ipc.server.on('start',function(){}); + */ + serve(callback?: () => void): void; + /** + * https://www.npmjs.com/package/node-ipc#serve + * Used to create local Unix Socket Server or Windows Socket Server to which Clients can bind. + * The server can emit events to specific Client Sockets, or broadcast events to all known Client Sockets + */ + serve(callback: null): void; + /** + * https://www.npmjs.com/package/node-ipc#servenet + * @param host If not specified this defaults to the first address in os.networkInterfaces(). + * For TCP, TLS & UDP servers this is most likely going to be 127.0.0.1 or ::1 + * @param port The port on which the TCP, UDP, or TLS Socket server will be bound, this defaults to 8000 if not specified + * @param UDPType If set this will create the server as a UDP socket. 'udp4' or 'udp6' are valid values. + * This defaults to not being set. When using udp6 make sure to specify a valid IPv6 host, like ::1 + * @param callback Function to be called when the server is created + */ + serveNet(host?: string, port?: number, UDPType?: "udp4" | "udp6", callback?: () => void): void; + /** + * https://www.npmjs.com/package/node-ipc#servenet + * @param UDPType If set this will create the server as a UDP socket. 'udp4' or 'udp6' are valid values. + * This defaults to not being set. When using udp6 make sure to specify a valid IPv6 host, like ::1 + * @param callback Function to be called when the server is created + */ + serveNet(UDPType: "udp4" | "udp6", callback?: () => void): void; + /** + * https://www.npmjs.com/package/node-ipc#servenet + * @param callback Function to be called when the server is created + * @param port The port on which the TCP, UDP, or TLS Socket server will be bound, this defaults to 8000 if not specified + */ + serveNet(callbackOrPort: EmptyCallback | number): void; + /** + * https://www.npmjs.com/package/node-ipc#servenet + * @param host If not specified this defaults to the first address in os.networkInterfaces(). + * For TCP, TLS & UDP servers this is most likely going to be 127.0.0.1 or ::1 + * @param port The port on which the TCP, UDP, or TLS Socket server will be bound, this defaults to 8000 if not specified + * @param callback Function to be called when the server is created + */ + serveNet(host: string, port: number, callback?: () => void): void; + /** + * This is where socket connection refrences will be stored when connecting to them as a client via the ipc.connectTo + * or iupc.connectToNet. They will be stored based on the ID used to create them, eg : ipc.of.mySocket + */ + of: any; + /** + * This is a refrence to the server created by ipc.serve or ipc.serveNet + */ + server: Server; + } + type EmptyCallback = () => void; + export interface Client { + /** + * triggered when a JSON message is received. The event name will be the type string from your message + * and the param will be the data object from your message eg : { type:'myEvent',data:{a:1}} + */ + on(event: string, callback: (...args: any[]) => void): Client; + /** + * triggered when an error has occured + */ + on(event: "error", callback: (err: any) => void): Client; + /** + * connect - triggered when socket connected + * disconnect - triggered by client when socket has disconnected from server + * destroy - triggered when socket has been totally destroyed, no further auto retries will happen and all references are gone + */ + on(event: "connect" | "disconnect" | "destroy", callback: () => void): Client; + /** + * triggered by server when a client socket has disconnected + */ + on(event: "socket.disconnected", callback: (socket: Socket, destroyedSocketID: string) => void): Client; + /** + * triggered when ipc.config.rawBuffer is true and a message is received + */ + on(event: "data", callback: (buffer: Buffer) => void): Client; + emit(event: string, value?: any): Client; + /** + * Unbind subscribed events + */ + off(event: string, handler: any): Client; + } + export interface Server extends Client { + /** + * start serving need top call serve or serveNet first to set up the server + */ + start(): void; + /** + * close the server and stop serving + */ + stop(): void; + emit(value: any): Client; + emit(event: string, value: any): Client; + emit(socket: Socket | SocketConfig, event: string, value?: any): Server; + emit(socketConfig: Socket | SocketConfig, value?: any): Server; + } + interface SocketConfig { + address?: string; + port?: number; + } + interface Config { + /** + * Default: 'app.' + * Used for Unix Socket (Unix Domain Socket) namespacing. + * If not set specifically, the Unix Domain Socket will combine the socketRoot, appspace, + * and id to form the Unix Socket Path for creation or binding. + * This is available incase you have many apps running on your system, you may have several sockets with the same id, + * but if you change the appspace, you will still have app specic unique sockets + */ + appspace: string; + /** + * Default: '/tmp/' + * The directory in which to create or bind to a Unix Socket + */ + socketRoot: string; + /** + * Default: os.hostname() + * The id of this socket or service + */ + id: string; + /** + * Default: 'localhost' + * The local or remote host on which TCP, TLS or UDP Sockets should connect + * Should resolve to 127.0.0.1 or ::1 see the table below related to this + */ + networkHost: string; + /** + * Default: 8000 + * The default port on which TCP, TLS, or UDP sockets should connect + */ + networkPort: number; + /** + * Default: 'utf8' + * the default encoding for data sent on sockets. Mostly used if rawBuffer is set to true. + * Valid values are : ascii utf8 utf16le ucs2 base64 hex + */ + encoding: "ascii" | "utf8" | "utf16le" | "ucs2" | "base64" | "hex"; + /** + * Default: false + * If true, data will be sent and received as a raw node Buffer NOT an Object as JSON. + * This is great for Binary or hex IPC, and communicating with other processes in languages like C and C++ + */ + rawBuffer: boolean; + /** + * Default: false + * Synchronous requests. Clients will not send new requests until the server answers + */ + sync: boolean; + /** + * Default: false + * Turn on/off logging default is false which means logging is on + */ + silent: boolean; + /** + * Default: true + * Turn on/off util.inspect colors for ipc.log + */ + logInColor: boolean; + /** + * Default: 5 + * Set the depth for util.inspect during ipc.log + */ + logDepth: number; + /** + * Default: console.log + * The function which receives the output from ipc.log; should take a single string argument + */ + logger(msg: string): void; + /** + * Default: 100 + * This is the max number of connections allowed to a socket. It is currently only being set on Unix Sockets. + * Other Socket types are using the system defaults + */ + maxConnections: number; + /** + * Default: 500 + * This is the time in milliseconds a client will wait before trying to reconnect to a server if the connection is lost. + * This does not effect UDP sockets since they do not have a client server relationship like Unix Sockets and TCP Sockets + */ + retry: number; + /* */ + /** + * Default: false + * if set, it represents the maximum number of retries after each disconnect before giving up + * and completely killing a specific connection + */ + maxRetries: boolean | number; + /** + * Default: false + * Defaults to false meaning clients will continue to retry to connect to servers indefinitely at the retry interval. + * If set to any number the client will stop retrying when that number is exceeded after each disconnect. + * If set to true in real time it will immediately stop trying to connect regardless of maxRetries. + * If set to 0, the client will NOT try to reconnect + */ + stopRetrying: boolean; + /** + * Default: true + * Defaults to true meaning that the module will take care of deleting the IPC socket prior to startup. + * If you use node-ipc in a clustered environment where there will be multiple listeners on the same socket, + * you must set this to false and then take care of deleting the socket in your own code. + */ + unlink: boolean; + /** + * Primarily used when specifying which interface a client should connect through. + * see the socket.connect documentation in the node.js api https://nodejs.org/api/net.html#net_socket_connect_options_connectlistener + */ + interfaces: { /** * Default: false - * If true, data will be sent and received as a raw node Buffer NOT an Object as JSON. - * This is great for Binary or hex IPC, and communicating with other processes in languages like C and C++ */ - rawBuffer: boolean; + localAddress?: boolean; /** * Default: false - * Synchronous requests. Clients will not send new requests until the server answers */ - sync: boolean; + localPort?: boolean; /** * Default: false - * Turn on/off logging default is false which means logging is on - */ - silent: boolean; - /** - * Default: true - * Turn on/off util.inspect colors for ipc.log - */ - logInColor: boolean; - /** - * Default: 5 - * Set the depth for util.inspect during ipc.log - */ - logDepth: number; - /** - * Default: console.log - * The function which receives the output from ipc.log; should take a single string argument */ - logger(msg: string): void; - /** - * Default: 100 - * This is the max number of connections allowed to a socket. It is currently only being set on Unix Sockets. - * Other Socket types are using the system defaults - */ - maxConnections: number; - /** - * Default: 500 - * This is the time in milliseconds a client will wait before trying to reconnect to a server if the connection is lost. - * This does not effect UDP sockets since they do not have a client server relationship like Unix Sockets and TCP Sockets - */ - retry: number; - /* */ + family?: boolean; /** * Default: false - * if set, it represents the maximum number of retries after each disconnect before giving up - * and completely killing a specific connection */ - maxRetries: boolean | number; + hints?: boolean; /** * Default: false - * Defaults to false meaning clients will continue to retry to connect to servers indefinitely at the retry interval. - * If set to any number the client will stop retrying when that number is exceeded after each disconnect. - * If set to true in real time it will immediately stop trying to connect regardless of maxRetries. - * If set to 0, the client will NOT try to reconnect - */ - stopRetrying: boolean; - /** - * Default: true - * Defaults to true meaning that the module will take care of deleting the IPC socket prior to startup. - * If you use node-ipc in a clustered environment where there will be multiple listeners on the same socket, - * you must set this to false and then take care of deleting the socket in your own code. */ - unlink: boolean; - /** - * Primarily used when specifying which interface a client should connect through. - * see the socket.connect documentation in the node.js api https://nodejs.org/api/net.html#net_socket_connect_options_connectlistener - */ - interfaces: { - /** - * Default: false - */ - localAddress?: boolean; - /** - * Default: false - */ - localPort?: boolean; - /** - * Default: false - */ - family?: boolean; - /** - * Default: false - */ - hints?: boolean; - /** - * Default: false - */ - lookup?: boolean; - }; - tls: { - rejectUnauthorized?: boolean; - public?: string; - private?: string; - }; - } + lookup?: boolean; + }; + tls: { + rejectUnauthorized?: boolean; + public?: string; + private?: string; + }; } - - export = NodeIPC - // declare const RootIPC: NodeIPC.IPC & { IPC: new () => NodeIPC.IPC }; - - // export = RootIPC; } +declare const RootIPC: NodeIPC.IPC & { IPC: new () => NodeIPC.IPC }; + +export = RootIPC; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..335ef91 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,11 @@ + +/** + * Add better assurance that a generated file name will be safe + */ +export function safeTitleFileName(title: string) { + title = title.replace(/(?<=\S):\s/g, " - "); + title = title.replace(/[\<\>\|\:\/\\]/g, " "); + title = title.replace(/\s\&\s/g, " and "); + title = title.trim(); + return title[0].toUpperCase() + title.slice(1); +} diff --git a/yarn.lock b/yarn.lock index aff1839..fe76cec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,6 +19,13 @@ dependencies: defer-to-connect "^1.0.1" +"@types/node-ipc@^9.1.3": + version "9.1.3" + resolved "https://registry.yarnpkg.com/@types/node-ipc/-/node-ipc-9.1.3.tgz#5381fbc910071083b28dd43225727877c108b361" + integrity sha512-ka7CPX9Dk2lwe4PxoZMLOwcQrtdcYe/7OKmH75fQbmt0jdKltWVkdGA81D5l55d0wNhkweHa3XmzFbt5C0ieOQ== + dependencies: + "@types/node" "*" + "@types/node@*", "@types/node@^14.14.41": version "14.14.41" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.41.tgz#d0b939d94c1d7bd53d04824af45f1139b8c45615" @@ -394,6 +401,14 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +diskusage@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/diskusage/-/diskusage-1.1.3.tgz#680d7dbf1b679168a195c9240eb3552cbd2c067b" + integrity sha512-EAyaxl8hy4Ph07kzlzGTfpbZMNAAAHXSZtNEMwdlnSd1noHzvA6HsgKt4fEMSvaEXQYLSphe5rPMxN4WOj0hcQ== + dependencies: + es6-promise "^4.2.5" + nan "^2.14.0" + dom-serializer@^1.0.1, dom-serializer@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.1.tgz#d845a1565d7c041a95e5dab62184ab41e3a519be" @@ -468,6 +483,11 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +es6-promise@^4.2.5: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -857,6 +877,11 @@ mz@^2.4.0: object-assign "^4.0.1" thenify-all "^1.0.0" +nan@^2.14.0: + version "2.14.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" + integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== + node-ipc@^9.1.4: version "9.1.4" resolved "https://registry.yarnpkg.com/node-ipc/-/node-ipc-9.1.4.tgz#2acf962681afdac2602876d98fe6434d54d9bd3c"