wip-feat: working tv season/episode monitor

This commit is contained in:
2025-05-07 22:13:38 -05:00
parent 527adb73c1
commit 25ff3e726d
9 changed files with 71 additions and 37 deletions

View File

@@ -29,14 +29,14 @@ services:
volumes: volumes:
- ./:/var/www - ./:/var/www
- ./var/download:/var/download - ./var/download:/var/download
command: php ./bin/console messenger:consume async -v --time-limit=3600 --limit=10 command: php ./bin/console messenger:consume async -vv --time-limit=3600
scheduler: scheduler:
build: . build: .
volumes: volumes:
- ./:/var/www - ./:/var/www
- ./var/download:/var/download - ./var/download:/var/download
command: php ./bin/console messenger:consume scheduler_movie_monitor -v --time-limit=3600 command: php ./bin/console messenger:consume scheduler_monitor -vv --time-limit=3600
mercure: mercure:
image: dunglas/mercure image: dunglas/mercure

View File

@@ -26,6 +26,9 @@ framework:
# 'App\Message\YourMessage': async # 'App\Message\YourMessage': async
'App\Download\Action\Command\DownloadMediaCommand': async 'App\Download\Action\Command\DownloadMediaCommand': async
'App\Download\Action\Command\MonitorTvEpisodeCommand': async 'App\Download\Action\Command\MonitorTvEpisodeCommand': async
'App\Download\Action\Command\MonitorTvSeasonCommand': async
'App\Download\Action\Command\MonitorTvShowCommand': async
'App\Download\Action\Command\MonitorMovieCommand': async
# when@test: # when@test:
# framework: # framework:

View File

@@ -34,10 +34,10 @@ class DownloadController extends AbstractController
) { ) {
$monitor = (new Monitor()) $monitor = (new Monitor())
->setUser($this->getUser()) ->setUser($this->getUser())
->setTmdbId('95396') ->setTmdbId('112442')
->setImdbId('tt11280740') ->setImdbId('tt9288860')
->setTitle('Severance') ->setTitle('Trash Truck')
->setMonitorType('tvshow') ->setMonitorType('tvseason')
->setSeason(1) ->setSeason(1)
->setEpisode(null) ->setEpisode(null)
->setCreatedAt(new DateTimeImmutable()) ->setCreatedAt(new DateTimeImmutable())
@@ -46,8 +46,8 @@ class DownloadController extends AbstractController
$this->monitorRepository->getEntityManager()->persist($monitor); $this->monitorRepository->getEntityManager()->persist($monitor);
$this->monitorRepository->getEntityManager()->flush(); $this->monitorRepository->getEntityManager()->flush();
$command = new MonitorTvShowCommand($monitor->getId()); $command = new MonitorTvSeasonCommand($monitor->getId());
$handler->handle($command); // $handler->handle($command);
return $this->json([ return $this->json([
'status' => 200, 'status' => 200,
'message' => $command 'message' => $command

View File

@@ -54,6 +54,8 @@ readonly class MonitorMovieHandler implements HandlerInterface
)); ));
$monitor->setStatus('Complete'); $monitor->setStatus('Complete');
$monitor->setDownloadedAt(new DateTimeIMmutable()); $monitor->setDownloadedAt(new DateTimeIMmutable());
} else {
$monitor->setStatus('Active');
} }
$monitor->setLastSearch(new DateTimeImmutable()); $monitor->setLastSearch(new DateTimeImmutable());

View File

@@ -10,6 +10,7 @@ use App\Download\Service\MonitorOptionEvaluator;
use App\Torrentio\Action\Command\GetMovieOptionsCommand; use App\Torrentio\Action\Command\GetMovieOptionsCommand;
use App\Torrentio\Action\Command\GetTvShowOptionsCommand; use App\Torrentio\Action\Command\GetTvShowOptionsCommand;
use App\Torrentio\Action\Handler\GetMovieOptionsHandler; use App\Torrentio\Action\Handler\GetMovieOptionsHandler;
use App\Torrentio\Action\Handler\GetTvShowOptionsHandler;
use DateTimeImmutable; use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use OneToMany\RichBundle\Contract\CommandInterface; use OneToMany\RichBundle\Contract\CommandInterface;
@@ -22,27 +23,29 @@ use Symfony\Component\Messenger\MessageBusInterface;
readonly class MonitorTvEpisodeHandler implements HandlerInterface readonly class MonitorTvEpisodeHandler implements HandlerInterface
{ {
public function __construct( public function __construct(
private MonitorRepository $movieMonitorRepository, private GetTvShowOptionsHandler $getTvShowOptionsHandler,
private GetMovieOptionsHandler $getMovieOptionsHandler,
private MonitorOptionEvaluator $monitorOptionEvaluator, private MonitorOptionEvaluator $monitorOptionEvaluator,
private EntityManagerInterface $entityManager, private EntityManagerInterface $entityManager,
private MessageBusInterface $bus, private MessageBusInterface $bus,
private LoggerInterface $logger, private LoggerInterface $logger,
private MonitorRepository $monitorRepository,
) {} ) {}
public function handle(CommandInterface $command): ResultInterface public function handle(CommandInterface $command): ResultInterface
{ {
$this->logger->info('> [MonitorTvEpisodeHandler] Executing MonitorTvEpisodeHandler'); $this->logger->info('> [MonitorTvEpisodeHandler] Executing MonitorTvEpisodeHandler');
$monitor = $this->movieMonitorRepository->find($command->movieMonitorId); $monitor = $this->monitorRepository->find($command->movieMonitorId);
$monitor->setStatus('In Progress'); $monitor->setStatus('In Progress');
$this->monitorRepository->getEntityManager()->flush();
$this->logger->info('> [MonitorTvEpisodeHandler] Searching for "' . $monitor->getTitle() . '" download options'); $this->logger->info('> [MonitorTvEpisodeHandler] Searching for "' . $monitor->getTitle() . '" season ' . $monitor->getSeason() . ' episode ' . $monitor->getEpisode() . ' download options');
$results = $this->getMovieOptionsHandler->handle( $results = $this->getTvShowOptionsHandler->handle(
new GetTvShowOptionsCommand( new GetTvShowOptionsCommand(
$monitor->getTmdbId(), $monitor->getTmdbId(),
$monitor->getImdbId(), $monitor->getImdbId(),
$monitor->getSeason(), $monitor->getSeason(),
$monitor->getEpisode()) $monitor->getEpisode()
)
); );
$this->logger->info('> [MonitorTvEpisodeHandler] Found ' . count($results->results) . ' download options'); $this->logger->info('> [MonitorTvEpisodeHandler] Found ' . count($results->results) . ' download options');
@@ -50,16 +53,19 @@ readonly class MonitorTvEpisodeHandler implements HandlerInterface
$result = $this->monitorOptionEvaluator->evaluateOptions($monitor, $results->results); $result = $this->monitorOptionEvaluator->evaluateOptions($monitor, $results->results);
if (null !== $result) { if (null !== $result) {
$this->logger->info('> [MonitorTvEpisodeHandler] 1 result found: dispatching DownloadMediaCommand for "' . $result->title . '"'); $this->logger->info('> [MonitorTvEpisodeHandler] 1 matching result found: dispatching DownloadMediaCommand for "' . $result->title . '"');
$this->bus->dispatch(new DownloadMediaCommand( $this->bus->dispatch(new DownloadMediaCommand(
$result->url, $result->url,
$monitor->getTitle(), $monitor->getTitle(),
$result->filename, $result->filename,
'movies', 'tvshows',
$monitor->getImdbId(), $monitor->getImdbId(),
)); ));
$monitor->setStatus('Complete'); $monitor->setStatus('Complete');
$monitor->setDownloadedAt(new DateTimeImmutable()); $monitor->setDownloadedAt(new DateTimeImmutable());
} else {
$this->logger->info('> [MonitorTvEpisodeHandler] 0 matching results found, monitor will run at next interval');
$monitor->setStatus('Active');
} }
$monitor->setLastSearch(new DateTimeImmutable()); $monitor->setLastSearch(new DateTimeImmutable());

View File

@@ -29,13 +29,12 @@ readonly class MonitorTvSeasonHandler implements HandlerInterface
{ {
public function __construct( public function __construct(
private MonitorRepository $monitorRepository, private MonitorRepository $monitorRepository,
private GetMovieOptionsHandler $getMovieOptionsHandler,
private MonitorOptionEvaluator $monitorOptionEvaluator,
private EntityManagerInterface $entityManager, private EntityManagerInterface $entityManager,
private MediaFiles $mediaFiles, private MediaFiles $mediaFiles,
private MessageBusInterface $bus, private MessageBusInterface $bus,
private LoggerInterface $logger, private LoggerInterface $logger,
private Tmdb $tmdb, private Tmdb $tmdb,
private MonitorTvEpisodeHandler $monitorTvEpisodeHandler,
) {} ) {}
public function handle(CommandInterface $command): ResultInterface public function handle(CommandInterface $command): ResultInterface
@@ -59,30 +58,43 @@ readonly class MonitorTvSeasonHandler implements HandlerInterface
// Dispatch Episode commands for each missing Episode // Dispatch Episode commands for each missing Episode
foreach ($episodesInSeason as $episode) { foreach ($episodesInSeason as $episode) {
if (!array_key_exists($episode['episode_number'], $downloadedEpisodes->toArray())) { $monitorCheck = $this->monitorRepository->findOneBy([
$monitor = (new Monitor()) 'imdbId' => $monitor->getImdbId(),
'title' => $monitor->getTitle(),
'monitorType' => 'tvepisode',
'season' => $monitor->getSeason(),
'episode' => $episode['episode_number'],
]);
$this->logger->info('> [MonitorTvSeasonHandler] Monitor exists for season ' . $monitor->getSeason() . ' episode ' . $episode['episode_number'] . ' for title: ' . $monitor->getTitle() . ' ? ' . (null !== $monitorCheck ? 'YES' : 'NO'));
if (!array_key_exists($episode['episode_number'], $downloadedEpisodes->toArray())
&& null === $monitorCheck
) {
$episodeMonitor = (new Monitor())
->setUser($monitor->getUser()) ->setUser($monitor->getUser())
->setTmdbId($monitor->getTmdbId()) ->setTmdbId($monitor->getTmdbId())
->setImdbId($monitor->getImdbId()) ->setImdbId($monitor->getImdbId())
->setTitle($monitor->getTitle()) ->setTitle($monitor->getTitle())
->setMonitorType('tvseason') ->setMonitorType('tvepisode')
->setSeason($monitor->getSeason()) ->setSeason($monitor->getSeason())
->setEpisode($episode['episode_number']) ->setEpisode($episode['episode_number'])
->setCreatedAt(new DateTimeImmutable()) ->setCreatedAt(new DateTimeImmutable())
->setSearchCount(0) ->setSearchCount(0)
->setStatus('New'); ->setStatus('New');
$this->monitorRepository->getEntityManager()->persist($monitor); $this->monitorRepository->getEntityManager()->persist($episodeMonitor);
$this->monitorRepository->getEntityManager()->flush(); $this->monitorRepository->getEntityManager()->flush();
$command = new MonitorTvEpisodeCommand($monitor->getId()); $command = new MonitorTvEpisodeCommand($episodeMonitor->getId());
$this->bus->dispatch($command); $this->monitorTvEpisodeHandler->handle($command);
$this->logger->info('> [MonitorTvSeasonHandler] Dispatching MonitorTvEpisodeCommand for season ' . $monitor->getSeason() . ' episode ' . $monitor->getEpisode() . ' for title: ' . $monitor->getTitle()); $this->logger->info('> [MonitorTvSeasonHandler] Dispatching MonitorTvEpisodeCommand for season ' . $episodeMonitor->getSeason() . ' episode ' . $episodeMonitor->getEpisode() . ' for title: ' . $monitor->getTitle());
} }
} }
$monitor->setLastSearch(new DateTimeImmutable()); $monitor->setLastSearch(new DateTimeImmutable());
$monitor->setSearchCount($monitor->getSearchCount() + 1); $monitor->setSearchCount($monitor->getSearchCount() + 1);
$monitor->setStatus('Complete');
$this->entityManager->flush(); $this->entityManager->flush();
return new MonitorMovieResult( return new MonitorMovieResult(

View File

@@ -86,6 +86,7 @@ readonly class MonitorTvShowHandler implements HandlerInterface
$monitor->setLastSearch(new DateTimeImmutable()); $monitor->setLastSearch(new DateTimeImmutable());
$monitor->setSearchCount($monitor->getSearchCount() + 1); $monitor->setSearchCount($monitor->getSearchCount() + 1);
$monitor->setStatus('Complete');
$this->entityManager->flush(); $this->entityManager->flush();
return new MonitorTvEpisodeResult( return new MonitorTvEpisodeResult(

View File

@@ -2,8 +2,10 @@
namespace App\Download\Framework\Scheduler; namespace App\Download\Framework\Scheduler;
use App\Download\Action\Handler\MonitorMovieHandler; use App\Download\Action\Command\MonitorMovieCommand;
use App\Download\Action\Handler\MonitorTvSeasonHandler; use App\Download\Action\Command\MonitorTvEpisodeCommand;
use App\Download\Action\Command\MonitorTvSeasonCommand;
use App\Download\Action\Command\MonitorTvShowCommand;
use App\Download\Framework\Repository\MonitorRepository; use App\Download\Framework\Repository\MonitorRepository;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\MessageBusInterface;
@@ -19,21 +21,24 @@ class MonitorDispatcher
) {} ) {}
public function __invoke() { public function __invoke() {
$this->logger->info('[MonitorDispatcher] Executing MovieMonitorDispatcher'); $this->logger->info('[MonitorDispatcher] Executing MonitorDispatcher');
$monitorHandlers = [ $monitorHandlers = [
'movie' => MonitorMovieHandler::class, 'movie' => MonitorMovieCommand::class,
'tvepisode' => MonitorTvSeasonHandler::class, 'tvepisode' => MonitorTvEpisodeCommand::class,
'tvseason' => MonitorTvSeasonHandler::class, 'tvseason' => MonitorTvSeasonCommand::class,
'tvshow' => MonitorTvSeasonHandler::class, 'tvshow' => MonitorTvShowCommand::class,
]; ];
$monitors = $this->monitorRepository->findBy(['status' => ['New', 'In Progress']]); $monitors = $this->monitorRepository->findBy(['status' => ['New', 'Active']]);
foreach ($monitors as $monitor) { foreach ($monitors as $monitor) {
$handler = $monitorHandlers[$monitor->getMonitorType()]; $monitor->setStatus('In Progress');
$this->logger->info('[MonitorDispatcher] Dispatching ' . $handler . ' for ' . $monitor->getTitle()); $this->monitorRepository->getEntityManager()->flush();
$this->bus->dispatch(new $handler($monitor->getId()));
$command = $monitorHandlers[$monitor->getMonitorType()];
$this->logger->info('[MonitorDispatcher] Dispatching ' . $command . ' for ' . $monitor->getTitle());
$this->bus->dispatch(new $command($monitor->getId()));
} }
} }
} }

View File

@@ -16,7 +16,7 @@ class MonitorOptionEvaluator
*/ */
public function evaluateOptions(Monitor $monitor, array $results): ?TorrentioResult public function evaluateOptions(Monitor $monitor, array $results): ?TorrentioResult
{ {
$sizeLow = 500; $sizeLow = 000;
$sizeHigh = 4096; $sizeHigh = 4096;
$bestMatches = []; $bestMatches = [];
@@ -48,6 +48,11 @@ class MonitorOptionEvaluator
) { ) {
$matches[] = $result; $matches[] = $result;
} }
if (($userPreferences['codec'] === null )
&& ($userPreferences['resolution'] === null )) {
$matches[] = $result;
}
} }
$sizeMatches = []; $sizeMatches = [];