wip: downloads entire season
This commit is contained in:
@@ -158,7 +158,7 @@ export default class extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
downloadSeason() {
|
downloadSeason() {
|
||||||
fetch(`/api/download/${this.imdbIdValue}/${this.activeFilter['season']}`, {
|
fetch(`/api/download/season/${this.imdbIdValue}/${this.activeFilter['season']}`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ framework:
|
|||||||
# Route your messages to the transports
|
# Route your messages to the transports
|
||||||
# 'App\Message\YourMessage': async
|
# 'App\Message\YourMessage': async
|
||||||
'App\Download\Action\Command\DownloadMediaCommand': async
|
'App\Download\Action\Command\DownloadMediaCommand': async
|
||||||
|
'App\Download\Action\Command\DownloadSeasonCommand': async
|
||||||
'App\Monitor\Action\Command\MonitorTvEpisodeCommand': async
|
'App\Monitor\Action\Command\MonitorTvEpisodeCommand': async
|
||||||
'App\Monitor\Action\Command\MonitorTvSeasonCommand': async
|
'App\Monitor\Action\Command\MonitorTvSeasonCommand': async
|
||||||
'App\Monitor\Action\Command\MonitorTvShowCommand': async
|
'App\Monitor\Action\Command\MonitorTvShowCommand': async
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# or pass your certificates into the 'app' container.
|
# or pass your certificates into the 'app' container.
|
||||||
# Please omit any trailing slashes. The APP_URL is
|
# Please omit any trailing slashes. The APP_URL is
|
||||||
# used to generate the Mercure URL behind the scenes.
|
# used to generate the Mercure URL behind the scenes.
|
||||||
APP_URL="https://torsearch.idocode.io"
|
APP_URL="https://dev.caldwell.digital"
|
||||||
APP_SECRET="70169beadfbc8101c393cbfbba27a313"
|
APP_SECRET="70169beadfbc8101c393cbfbba27a313"
|
||||||
APP_ENV=prod
|
APP_ENV=prod
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,16 @@
|
|||||||
services:
|
services:
|
||||||
|
caddy:
|
||||||
|
image: caddy:2.9.1
|
||||||
|
restart: unless-stopped
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
- "443:443/udp"
|
||||||
|
volumes:
|
||||||
|
- $PWD/../../bash/caddy:/etc/caddy
|
||||||
|
- $PWD/../../bash/certs:/etc/ssl
|
||||||
# The "entrypoint" into the application. This reverse proxy
|
# The "entrypoint" into the application. This reverse proxy
|
||||||
# proxies traffic back to their respective services. If not
|
# proxies traffic back to their respective services. If not
|
||||||
# running behind a reverse proxy inject your SSL certificates
|
# running behind a reverse proxy inject your SSL certificates
|
||||||
@@ -12,8 +24,8 @@ services:
|
|||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
volumes:
|
volumes:
|
||||||
- /mnt/media/downloads/movies:/var/download/movies
|
- ./downloads/movies:/var/download/movies
|
||||||
- /mnt/media/downloads/tvshows:/var/download/tvshows
|
- ./downloads/tvshows:/var/download/tvshows
|
||||||
environment:
|
environment:
|
||||||
TZ: America/Chicago
|
TZ: America/Chicago
|
||||||
MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
||||||
@@ -32,8 +44,8 @@ services:
|
|||||||
worker:
|
worker:
|
||||||
image: code.caldwell.digital/home/torsearch-worker:latest
|
image: code.caldwell.digital/home/torsearch-worker:latest
|
||||||
volumes:
|
volumes:
|
||||||
- /mnt/media/downloads/movies:/var/download/movies
|
- ./downloads/movies:/var/download/movies
|
||||||
- /mnt/media/downloads/tvshows:/var/download/tvshows
|
- ./downloads/tvshows:/var/download/tvshows
|
||||||
environment:
|
environment:
|
||||||
TZ: America/Chicago
|
TZ: America/Chicago
|
||||||
command: -vvv
|
command: -vvv
|
||||||
@@ -52,8 +64,8 @@ services:
|
|||||||
scheduler:
|
scheduler:
|
||||||
image: code.caldwell.digital/home/torsearch-scheduler:latest
|
image: code.caldwell.digital/home/torsearch-scheduler:latest
|
||||||
volumes:
|
volumes:
|
||||||
- /mnt/media/downloads/movies:/var/download/movies
|
- ./downloads/movies:/var/download/movies
|
||||||
- /mnt/media/downloads/tvshows:/var/download/tvshows
|
- ./downloads/tvshows:/var/download/tvshows
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
47
migrations/Version20250708033046.php
Normal file
47
migrations/Version20250708033046.php
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20250708033046 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP TABLE sessions
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE download CHANGE created_at created_at DATETIME NOT NULL, CHANGE updated_at updated_at DATETIME NOT NULL
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE monitor ADD only_future TINYINT(1) NOT NULL DEFAULT 1
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE sessions (sess_id VARBINARY(128) NOT NULL, sess_data LONGBLOB NOT NULL, sess_lifetime INT UNSIGNED NOT NULL, sess_time INT UNSIGNED NOT NULL, INDEX sess_lifetime_idx (sess_lifetime), PRIMARY KEY(sess_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_bin` ENGINE = InnoDB COMMENT = ''
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE monitor DROP only_future
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE download CHANGE created_at created_at DATETIME DEFAULT NULL, CHANGE updated_at updated_at DATETIME DEFAULT NULL
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,15 +7,11 @@ use App\User\Framework\Entity\UserPreference;
|
|||||||
use App\User\Framework\Repository\PreferenceOptionRepository;
|
use App\User\Framework\Repository\PreferenceOptionRepository;
|
||||||
use App\User\Framework\Repository\PreferencesRepository;
|
use App\User\Framework\Repository\PreferencesRepository;
|
||||||
use App\User\Framework\Repository\UserRepository;
|
use App\User\Framework\Repository\UserRepository;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor;
|
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
|
|
||||||
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
|
|
||||||
|
|
||||||
#[AsCommand(
|
#[AsCommand(
|
||||||
name: 'db:seed',
|
name: 'db:seed',
|
||||||
|
|||||||
18
src/Download/Action/Command/DownloadSeasonCommand.php
Normal file
18
src/Download/Action/Command/DownloadSeasonCommand.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Download\Action\Command;
|
||||||
|
|
||||||
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implements CommandInterface<DownloadSeasonCommand>
|
||||||
|
*/
|
||||||
|
class DownloadSeasonCommand implements CommandInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public int $userId,
|
||||||
|
public int $season,
|
||||||
|
public string $imdbId,
|
||||||
|
public string $mediaType = 'tvshows',
|
||||||
|
) {}
|
||||||
|
}
|
||||||
110
src/Download/Action/Handler/DownloadSeasonHandler.php
Normal file
110
src/Download/Action/Handler/DownloadSeasonHandler.php
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Download\Action\Handler;
|
||||||
|
|
||||||
|
use Aimeos\Map;
|
||||||
|
use App\Download\Action\Command\DownloadMediaCommand;
|
||||||
|
use App\Download\Action\Command\DownloadSeasonCommand;
|
||||||
|
use App\Download\Action\Result\DownloadMediaResult;
|
||||||
|
use App\Download\Action\Result\DownloadSeasonResult;
|
||||||
|
use App\Download\DownloadOptionEvaluator;
|
||||||
|
use App\Download\Framework\Repository\DownloadRepository;
|
||||||
|
use App\Download\Downloader\DownloaderInterface;
|
||||||
|
use App\Monitor\Action\Command\MonitorTvEpisodeCommand;
|
||||||
|
use App\Monitor\Action\Handler\MonitorTvEpisodeHandler;
|
||||||
|
use App\Monitor\Framework\Entity\Monitor;
|
||||||
|
use App\Monitor\Service\MediaFiles;
|
||||||
|
use App\Tmdb\Tmdb;
|
||||||
|
use App\Torrentio\Action\Command\GetTvShowOptionsCommand;
|
||||||
|
use App\Torrentio\Action\Handler\GetTvShowOptionsHandler;
|
||||||
|
use App\User\Dto\UserPreferencesFactory;
|
||||||
|
use App\User\Framework\Repository\UserRepository;
|
||||||
|
use Nihilarr\PTN;
|
||||||
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
|
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||||
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
|
|
||||||
|
/** @implements HandlerInterface<DownloadSeasonCommand, DownloadMediaResult> */
|
||||||
|
readonly class DownloadSeasonHandler implements HandlerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private MediaFiles $mediaFiles,
|
||||||
|
private LoggerInterface $logger,
|
||||||
|
private Tmdb $tmdb,
|
||||||
|
private MessageBusInterface $bus,
|
||||||
|
private DownloadOptionEvaluator $downloadOptionEvaluator,
|
||||||
|
private GetTvShowOptionsHandler $getTvShowOptionsHandler,
|
||||||
|
private UserRepository $userRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function handle(CommandInterface $command): ResultInterface
|
||||||
|
{
|
||||||
|
$series = $this->tmdb->mediaDetails($command->imdbId, $command->mediaType);
|
||||||
|
$this->logger->info('> [DownloadTvSeasonHandler] Executing DownloadTvSeasonHandler for "' . $series->title . '" season ' . $command->season);
|
||||||
|
|
||||||
|
$episodesInSeason = Map::from($series->episodes[$command->season]);
|
||||||
|
$this->logger->info('> [DownloadTvSeasonHandler] ...Found ' . count($episodesInSeason) . ' episodes in season ' . $command->season);
|
||||||
|
|
||||||
|
$downloadCommands = [];
|
||||||
|
foreach ($episodesInSeason as $episode) {
|
||||||
|
$this->logger->info('> [DownloadTvSeasonHandler] ...Evaluating episode ' . $episode['episode_number']);
|
||||||
|
|
||||||
|
$results = $this->getTvShowOptionsHandler->handle(
|
||||||
|
new GetTvShowOptionsCommand(
|
||||||
|
$series->tmdbId,
|
||||||
|
$command->imdbId,
|
||||||
|
$command->season,
|
||||||
|
$episode['episode_number']
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->logger->info('> [DownloadTvSeasonHandler] ...Found ' . count($results->results) . ' total download options, beginning evaluation');
|
||||||
|
|
||||||
|
$userPreferences = UserPreferencesFactory::createFromUser(
|
||||||
|
$this->userRepository->findOneBy(['id' => $command->userId])
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $this->downloadOptionEvaluator->evaluateOptions($results->results, $userPreferences);
|
||||||
|
|
||||||
|
if (null !== $result) {
|
||||||
|
$this->logger->info('> [DownloadTvSeasonHandler] ......Found 1 matching result: dispatching DownloadMediaCommand for "' . $result->title . '"');
|
||||||
|
$downloadCommand = new DownloadMediaCommand(
|
||||||
|
$result->url,
|
||||||
|
$series->title,
|
||||||
|
$result->filename,
|
||||||
|
'tvshows',
|
||||||
|
$command->imdbId,
|
||||||
|
$command->userId,
|
||||||
|
);
|
||||||
|
$this->bus->dispatch($downloadCommand);
|
||||||
|
$downloadCommands[] = $downloadCommand;
|
||||||
|
} else {
|
||||||
|
$this->logger->info('> [DownloadTvSeasonHandler] ......Found 0 matching results');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DownloadSeasonResult(
|
||||||
|
status: 200,
|
||||||
|
message: 'Success',
|
||||||
|
data: ['downloads' => $downloadCommands],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDownloadedEpisodes(string $title)
|
||||||
|
{
|
||||||
|
// Check current episodes
|
||||||
|
$downloadedEpisodes = $this->mediaFiles
|
||||||
|
->getEpisodes($title)
|
||||||
|
->map(fn($episode) => (object) (new PTN())->parse($episode))
|
||||||
|
->filter(fn ($episode) =>
|
||||||
|
property_exists($episode, 'episode')
|
||||||
|
&& property_exists($episode, 'season')
|
||||||
|
&& null !== $episode->episode
|
||||||
|
&& null !== $episode->season
|
||||||
|
)
|
||||||
|
->rekey(fn($episode) => $episode->episode);
|
||||||
|
$this->logger->info('> [MonitorTvSeasonHandler] Found ' . count($downloadedEpisodes) . ' downloaded episodes for title: ' . $monitor->getTitle());
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/Download/Action/Input/DownloadSeasonInput.php
Normal file
37
src/Download/Action/Input/DownloadSeasonInput.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Download\Action\Input;
|
||||||
|
|
||||||
|
use App\Download\Action\Command\DownloadMediaCommand;
|
||||||
|
use App\Download\Action\Command\DownloadSeasonCommand;
|
||||||
|
use OneToMany\RichBundle\Attribute\SourceRequest;
|
||||||
|
use OneToMany\RichBundle\Attribute\SourceRoute;
|
||||||
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
|
use OneToMany\RichBundle\Contract\InputInterface;
|
||||||
|
|
||||||
|
/** @implements InputInterface<DownloadSeasonInput> */
|
||||||
|
class DownloadSeasonInput implements InputInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
#[SourceRoute('imdbId')]
|
||||||
|
public string $imdbId,
|
||||||
|
|
||||||
|
#[SourceRoute('season')]
|
||||||
|
public int $season,
|
||||||
|
|
||||||
|
#[SourceRequest('mediaType')]
|
||||||
|
public string $mediaType = 'tvshows',
|
||||||
|
|
||||||
|
public ?int $userId = null,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function toCommand(): CommandInterface
|
||||||
|
{
|
||||||
|
return new DownloadSeasonCommand(
|
||||||
|
$this->userId,
|
||||||
|
$this->season,
|
||||||
|
$this->imdbId,
|
||||||
|
$this->mediaType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/Download/Action/Result/DownloadSeasonResult.php
Normal file
15
src/Download/Action/Result/DownloadSeasonResult.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Download\Action\Result;
|
||||||
|
|
||||||
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
|
|
||||||
|
/** @implements ResultInterface<DownloadSeasonResult> */
|
||||||
|
class DownloadSeasonResult implements ResultInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public int $status,
|
||||||
|
public string $message,
|
||||||
|
public array $data,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
94
src/Download/DownloadOptionEvaluator.php
Normal file
94
src/Download/DownloadOptionEvaluator.php
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Download;
|
||||||
|
|
||||||
|
use Aimeos\Map;
|
||||||
|
use App\Monitor\Framework\Entity\Monitor;
|
||||||
|
use App\Torrentio\Result\TorrentioResult;
|
||||||
|
use App\User\Dto\UserPreferences;
|
||||||
|
|
||||||
|
class DownloadOptionEvaluator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Monitor $monitor
|
||||||
|
* @param TorrentioResult[] $results
|
||||||
|
* @return TorrentioResult|null
|
||||||
|
* @throws \Throwable
|
||||||
|
*/
|
||||||
|
public function evaluateOptions(array $results, UserPreferences $userPreferences): ?TorrentioResult
|
||||||
|
{
|
||||||
|
$sizeLow = 000;
|
||||||
|
$sizeHigh = 4096;
|
||||||
|
|
||||||
|
$bestMatches = [];
|
||||||
|
$matches = [];
|
||||||
|
|
||||||
|
foreach ($results as $result) {
|
||||||
|
if (!in_array($userPreferences->language, $result->languages)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result->resolution === $userPreferences->resolution
|
||||||
|
&& $result->codec === $userPreferences->codec
|
||||||
|
) {
|
||||||
|
$bestMatches[] = $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($userPreferences->resolution === '2160p'
|
||||||
|
&& $userPreferences->codec === $result->codec
|
||||||
|
&& $result->resolution === '1080p'
|
||||||
|
) {
|
||||||
|
$matches[] = $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($userPreferences->codec === 'h264'
|
||||||
|
&& $userPreferences->resolution === $result->resolution
|
||||||
|
&& $result->codec === 'h265'
|
||||||
|
) {
|
||||||
|
$matches[] = $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($userPreferences->codec === null )
|
||||||
|
&& ($userPreferences->resolution === null )) {
|
||||||
|
$matches[] = $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sizeMatches = [];
|
||||||
|
|
||||||
|
foreach ($bestMatches as $result) {
|
||||||
|
if (str_contains($result->size, 'GB')) {
|
||||||
|
$size = (int) trim(str_replace('GB', '', $result->size)) * 1024;
|
||||||
|
} else {
|
||||||
|
$size = (int) trim(str_replace('MB', '', $result->size));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($size > $sizeLow && $size < $sizeHigh) {
|
||||||
|
$sizeMatches[] = $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($sizeMatches)) {
|
||||||
|
return Map::from($sizeMatches)->usort(fn($a, $b) => $a->seeders <=> $b->seeders)->last();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($matches as $result) {
|
||||||
|
$size = 0;
|
||||||
|
if (str_contains($result->size, 'GB')) {
|
||||||
|
$size = (int) trim(str_replace('GB', '', $result->size)) * 1024;
|
||||||
|
} else {
|
||||||
|
$size = (int) trim(str_replace('MB', '', $result->size));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($size > $sizeLow && $size < $sizeHigh) {
|
||||||
|
$sizeMatches[] = $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($sizeMatches)) {
|
||||||
|
return Map::from($sizeMatches)->usort(fn($a, $b) => $a->seeders <=> $b->seeders)->last();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,13 +4,16 @@ namespace App\Download\Framework\Controller;
|
|||||||
|
|
||||||
use App\Base\Util\Broadcaster;
|
use App\Base\Util\Broadcaster;
|
||||||
use App\Download\Action\Handler\DeleteDownloadHandler;
|
use App\Download\Action\Handler\DeleteDownloadHandler;
|
||||||
|
use App\Download\Action\Handler\DownloadSeasonHandler;
|
||||||
use App\Download\Action\Handler\PauseDownloadHandler;
|
use App\Download\Action\Handler\PauseDownloadHandler;
|
||||||
use App\Download\Action\Handler\ResumeDownloadHandler;
|
use App\Download\Action\Handler\ResumeDownloadHandler;
|
||||||
use App\Download\Action\Input\DeleteDownloadInput;
|
use App\Download\Action\Input\DeleteDownloadInput;
|
||||||
use App\Download\Action\Input\DownloadMediaInput;
|
use App\Download\Action\Input\DownloadMediaInput;
|
||||||
|
use App\Download\Action\Input\DownloadSeasonInput;
|
||||||
use App\Download\Action\Input\PauseDownloadInput;
|
use App\Download\Action\Input\PauseDownloadInput;
|
||||||
use App\Download\Action\Input\ResumeDownloadInput;
|
use App\Download\Action\Input\ResumeDownloadInput;
|
||||||
use App\Download\Framework\Repository\DownloadRepository;
|
use App\Download\Framework\Repository\DownloadRepository;
|
||||||
|
use App\User\Dto\UserPreferencesFactory;
|
||||||
use Nihilarr\PTN;
|
use Nihilarr\PTN;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
@@ -105,4 +108,18 @@ class ApiController extends AbstractController
|
|||||||
|
|
||||||
return $this->json(['status' => 200, 'message' => 'Download Resumed']);
|
return $this->json(['status' => 200, 'message' => 'Download Resumed']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/api/download/season/{imdbId}/{season}', name: 'api_download_season', methods: ['GET'])]
|
||||||
|
public function downloadSeason(
|
||||||
|
DownloadSeasonInput $input,
|
||||||
|
): Response {
|
||||||
|
$input->userId = $this->getUser()->getId();
|
||||||
|
$this->bus->dispatch($input->toCommand());
|
||||||
|
$this->broadcaster->alert(
|
||||||
|
title: 'Success',
|
||||||
|
message: "Your download for season $input->season has been added to the queue.",
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->json(['status' => 200, 'message' => 'Download Resumed']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ class Monitor
|
|||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
private ?int $searchCount = null;
|
private ?int $searchCount = null;
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private bool $onlyFuture = true;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
|
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
|
||||||
private ?\DateTimeInterface $lastSearch = null;
|
private ?\DateTimeInterface $lastSearch = null;
|
||||||
|
|
||||||
@@ -147,6 +150,11 @@ class Monitor
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isOnlyFuture(): bool
|
||||||
|
{
|
||||||
|
return $this->onlyFuture;
|
||||||
|
}
|
||||||
|
|
||||||
public function getLastSearch(): ?\DateTimeInterface
|
public function getLastSearch(): ?\DateTimeInterface
|
||||||
{
|
{
|
||||||
return Carbon::parse($this->lastSearch);
|
return Carbon::parse($this->lastSearch);
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ class UserPreferences
|
|||||||
{
|
{
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public readonly string $resolution,
|
public readonly ?string $resolution,
|
||||||
public readonly string $codec,
|
public readonly ?string $codec,
|
||||||
public readonly string $language,
|
public readonly ?string $language,
|
||||||
public readonly string $provider,
|
public readonly ?string $provider,
|
||||||
public readonly string $quality,
|
public readonly ?string $quality,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,46 @@
|
|||||||
|
|
||||||
namespace App\User\Dto;
|
namespace App\User\Dto;
|
||||||
|
|
||||||
|
use App\User\Framework\Entity\PreferenceOption;
|
||||||
use App\User\Framework\Entity\User;
|
use App\User\Framework\Entity\User;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
|
||||||
class UserPreferencesFactory
|
class UserPreferencesFactory
|
||||||
{
|
{
|
||||||
public static function createFromUser(User $user): UserPreferences
|
/** @param User $user */
|
||||||
|
public static function createFromUser(UserInterface $user): UserPreferences
|
||||||
{
|
{
|
||||||
return new UserPreferences(
|
return new UserPreferences(
|
||||||
resolution: $user->getUserPreference('resolution')->getPreferenceValue(),
|
resolution: self::getNestedValue($user, 'resolution'),
|
||||||
codec: $user->getUserPreference('codec')->getPreferenceValue(),
|
codec: self::getNestedValue($user, 'codec'),
|
||||||
language: $user->getUserPreference('language')->getPreferenceValue(),
|
language: self::getValue($user, 'language'),
|
||||||
provider: $user->getUserPreference('provider')->getPreferenceValue(),
|
provider: self::getValue($user, 'provider'),
|
||||||
quality: $user->getUserPreference('quality')->getPreferenceValue(),
|
quality: self::getValue($user, 'quality'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param User $user */
|
||||||
|
private static function getValue(UserInterface $user, string $preferenceId)
|
||||||
|
{
|
||||||
|
$value = $user->getUserPreference($preferenceId)->getPreferenceValue();
|
||||||
|
if ($value === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param User $user */
|
||||||
|
private static function getNestedValue(UserInterface $user, string $preferenceId): ?string
|
||||||
|
{
|
||||||
|
$preference = $user->getUserPreference($preferenceId);
|
||||||
|
if (null === $preference) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $preference->getPreference()
|
||||||
|
->getPreferenceOptions()
|
||||||
|
->filter(fn (PreferenceOption $option) => (string) $option->getId() === $preference->getPreferenceValue())
|
||||||
|
->first()
|
||||||
|
->getValue()
|
||||||
|
;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,7 +156,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||||||
public function getUserPreference(string $preferenceName): ?UserPreference
|
public function getUserPreference(string $preferenceName): ?UserPreference
|
||||||
{
|
{
|
||||||
foreach ($this->userPreferences as $userPreference) {
|
foreach ($this->userPreferences as $userPreference) {
|
||||||
if ($userPreference->getPreference()->getName() === $preferenceName) {
|
if ($userPreference->getPreference()->getName() === $preferenceName
|
||||||
|
|| $userPreference->getPreference()->getId() === $preferenceName
|
||||||
|
) {
|
||||||
return $userPreference;
|
return $userPreference;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user