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