feat: notifies user of bad RD download (failed for copyright, etc.)
Some checks failed
SonarQube Scan / SonarQube Trigger (push) Failing after 13s

This commit is contained in:
Brock H Caldwell
2026-02-06 09:55:19 -06:00
parent 37516c7f02
commit f4644d40ef
6 changed files with 61 additions and 8 deletions

View File

@@ -24,10 +24,10 @@ readonly class Broadcaster
private LoggerInterface $logger, private LoggerInterface $logger,
) {} ) {}
public function alert(string $title, string $message, string $type = "success", bool $sendPush = false): void public function alert(string $title, string $message, string $type = "success", bool $sendPush = false, ?string $mercureAlertTopic = null): void
{ {
try { try {
$userAlertTopic = $this->requestStack->getCurrentRequest()->getSession()->get('mercure_alert_topic'); $userAlertTopic = $mercureAlertTopic ?? $this->requestStack->getCurrentRequest()->getSession()->get('mercure_alert_topic');
$update = new Update( $update = new Update(
$userAlertTopic, $userAlertTopic,
$this->renderer->render('broadcast/Alert.stream.html.twig', [ $this->renderer->render('broadcast/Alert.stream.html.twig', [
@@ -39,7 +39,7 @@ readonly class Broadcaster
); );
$this->hub->publish($update); $this->hub->publish($update);
} catch (\Throwable $exception) { } catch (\Throwable $exception) {
// ToDo: look for better handling to get message to end user $this->logger->error('Unable to publish alert: ' . $exception->getMessage());
} }
if (true === $sendPush && in_array($this->notificationTransport, ['ntfy'])) { if (true === $sendPush && in_array($this->notificationTransport, ['ntfy'])) {

View File

@@ -17,5 +17,6 @@ class DownloadMediaCommand implements CommandInterface
public string $imdbId, public string $imdbId,
public int $userId, public int $userId,
public ?int $downloadId = null, public ?int $downloadId = null,
public ?string $mercureAlertTopic = null,
) {} ) {}
} }

View File

@@ -2,9 +2,12 @@
namespace App\Download\Action\Handler; namespace App\Download\Action\Handler;
use App\Base\Enum\MediaType;
use App\Base\Service\Broadcaster;
use App\Download\Action\Command\DownloadMediaCommand; use App\Download\Action\Command\DownloadMediaCommand;
use App\Download\Action\Result\DownloadMediaResult; use App\Download\Action\Result\DownloadMediaResult;
use App\Download\DownloadEvents; use App\Download\DownloadEvents;
use App\Download\Framework\Entity\Download;
use App\Download\Framework\Repository\DownloadRepository; use App\Download\Framework\Repository\DownloadRepository;
use App\Download\Downloader\DownloaderInterface; use App\Download\Downloader\DownloaderInterface;
use App\EventLog\Action\Command\AddEventLogCommand; use App\EventLog\Action\Command\AddEventLogCommand;
@@ -22,7 +25,7 @@ readonly class DownloadMediaHandler implements HandlerInterface
private MessageBusInterface $bus, private MessageBusInterface $bus,
private DownloaderInterface $downloader, private DownloaderInterface $downloader,
private DownloadRepository $downloadRepository, private DownloadRepository $downloadRepository,
private UserRepository $userRepository, private UserRepository $userRepository, private Broadcaster $broadcaster,
) {} ) {}
public function handle(CommandInterface $command): ResultInterface public function handle(CommandInterface $command): ResultInterface
@@ -49,6 +52,16 @@ readonly class DownloadMediaHandler implements HandlerInterface
$download = $this->downloadRepository->find($command->downloadId); $download = $this->downloadRepository->find($command->downloadId);
} }
try {
$this->validateDownloadUrl($download->getUrl());
} catch (\Throwable $exception) {
$download->setProgress(100);
$download->setStatus('Failed');
$this->downloadRepository->getEntityManager()->flush();
$this->sendFailedDownloadAlert($download, $command->mercureAlertTopic, $exception->getMessage());
return new DownloadMediaResult(400, $exception->getMessage());
}
try { try {
if ($download->getStatus() !== 'Paused') { if ($download->getStatus() !== 'Paused') {
$this->downloadRepository->updateStatus($download->getId(), 'In Progress'); $this->downloadRepository->updateStatus($download->getId(), 'In Progress');
@@ -77,4 +90,30 @@ readonly class DownloadMediaHandler implements HandlerInterface
)); ));
return new DownloadMediaResult(200, "Success."); return new DownloadMediaResult(200, "Success.");
} }
public function validateDownloadUrl(string $downloadUrl)
{
$badFileSizes = [
2119075, // copyright infringement
];
$badFileLocations = [
'https://torrentio.strem.fun/videos/failed_infringement_v2.mp4' => 'Removed for Copyright Infringement.',
];
$headers = get_headers($downloadUrl, true);
if (array_key_exists($headers['Location'], $badFileLocations)) {
throw new \Exception($badFileLocations[$headers['Location']]);
}
}
private function sendFailedDownloadAlert(Download $download, string $mercureAlertTopic, ?string $message = null): void
{
$this->broadcaster->alert(
title: 'Download Failed',
message: $message ?? "{$download->getTitle()} failed to download.",
type: 'warning',
mercureAlertTopic: $mercureAlertTopic
);
}
} }

View File

@@ -10,6 +10,8 @@ use OneToMany\RichBundle\Contract\InputInterface;
/** @implements InputInterface<DownloadMediaInput> */ /** @implements InputInterface<DownloadMediaInput> */
class DownloadMediaInput implements InputInterface class DownloadMediaInput implements InputInterface
{ {
public ?string $mercureAlertTopic = null;
public function __construct( public function __construct(
#[SourceRequest('url')] #[SourceRequest('url')]
public string $url, public string $url,
@@ -44,6 +46,7 @@ class DownloadMediaInput implements InputInterface
$this->imdbId, $this->imdbId,
$this->userId, $this->userId,
$this->downloadId, $this->downloadId,
$this->mercureAlertTopic,
); );
} }
} }

View File

@@ -15,6 +15,7 @@ use App\Download\DownloadEvents;
use App\Download\Framework\Repository\DownloadRepository; use App\Download\Framework\Repository\DownloadRepository;
use App\EventLog\Action\Command\AddEventLogCommand; use App\EventLog\Action\Command\AddEventLogCommand;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
@@ -24,7 +25,7 @@ class ApiController extends AbstractController
public function __construct( public function __construct(
private DownloadRepository $downloadRepository, private DownloadRepository $downloadRepository,
private MessageBusInterface $bus, private MessageBusInterface $bus,
private readonly Broadcaster $broadcaster, private readonly Broadcaster $broadcaster, private readonly RequestStack $requestStack,
) {} ) {}
#[Route('/api/download', name: 'api_download', methods: ['POST'])] #[Route('/api/download', name: 'api_download', methods: ['POST'])]
@@ -42,6 +43,7 @@ class ApiController extends AbstractController
); );
$input->downloadId = $download->getId(); $input->downloadId = $download->getId();
$input->userId = $this->getUser()->getId(); $input->userId = $this->getUser()->getId();
$input->mercureAlertTopic = $this->requestStack->getSession()->get('mercure_alert_topic');
$this->bus->dispatch(new AddEventLogCommand( $this->bus->dispatch(new AddEventLogCommand(
$this->getUser(), $this->getUser(),

View File

@@ -98,6 +98,14 @@ class DownloadRepository extends ServiceEntityRepository
return $download; return $download;
} }
public function updateProgress(int $id, int $progress): Download
{
$download = $this->find($id);
$download->setProgress($progress);
$this->getEntityManager()->flush();
return $download;
}
public function delete(int $id) public function delete(int $id)
{ {
$entity = $this->find($id); $entity = $this->find($id);