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,
) {}
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 {
$userAlertTopic = $this->requestStack->getCurrentRequest()->getSession()->get('mercure_alert_topic');
$userAlertTopic = $mercureAlertTopic ?? $this->requestStack->getCurrentRequest()->getSession()->get('mercure_alert_topic');
$update = new Update(
$userAlertTopic,
$this->renderer->render('broadcast/Alert.stream.html.twig', [
@@ -39,7 +39,7 @@ readonly class Broadcaster
);
$this->hub->publish($update);
} 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'])) {

View File

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

View File

@@ -2,9 +2,12 @@
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\Result\DownloadMediaResult;
use App\Download\DownloadEvents;
use App\Download\Framework\Entity\Download;
use App\Download\Framework\Repository\DownloadRepository;
use App\Download\Downloader\DownloaderInterface;
use App\EventLog\Action\Command\AddEventLogCommand;
@@ -21,8 +24,8 @@ readonly class DownloadMediaHandler implements HandlerInterface
public function __construct(
private MessageBusInterface $bus,
private DownloaderInterface $downloader,
private DownloadRepository $downloadRepository,
private UserRepository $userRepository,
private DownloadRepository $downloadRepository,
private UserRepository $userRepository, private Broadcaster $broadcaster,
) {}
public function handle(CommandInterface $command): ResultInterface
@@ -49,6 +52,16 @@ readonly class DownloadMediaHandler implements HandlerInterface
$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 {
if ($download->getStatus() !== 'Paused') {
$this->downloadRepository->updateStatus($download->getId(), 'In Progress');
@@ -77,4 +90,30 @@ readonly class DownloadMediaHandler implements HandlerInterface
));
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> */
class DownloadMediaInput implements InputInterface
{
public ?string $mercureAlertTopic = null;
public function __construct(
#[SourceRequest('url')]
public string $url,
@@ -44,6 +46,7 @@ class DownloadMediaInput implements InputInterface
$this->imdbId,
$this->userId,
$this->downloadId,
$this->mercureAlertTopic,
);
}
}

View File

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

View File

@@ -98,6 +98,14 @@ class DownloadRepository extends ServiceEntityRepository
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)
{
$entity = $this->find($id);