Compare commits
5 Commits
dev-turbo-
...
v0.25.3
| Author | SHA1 | Date | |
|---|---|---|---|
| dc2845d74d | |||
| 5e722dcbc7 | |||
| a126871af8 | |||
| 70f551cea9 | |||
| 4824c2d344 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,3 +18,4 @@ bolt.db
|
||||
###> phpstan/phpstan ###
|
||||
phpstan.neon
|
||||
###< phpstan/phpstan ###
|
||||
.php-cs-fixer.cache
|
||||
|
||||
@@ -6,6 +6,7 @@ import './bootstrap.js';
|
||||
* which should already be in your base.html.twig.
|
||||
*/
|
||||
import './styles/app.css';
|
||||
import PullToRefresh from 'pulltorefreshjs';
|
||||
|
||||
console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉');
|
||||
|
||||
@@ -18,3 +19,10 @@ var observer = new MutationObserver(function(mutations) {
|
||||
|
||||
observer.observe(document, {attributes: false, childList: true, characterData: false, subtree:true});
|
||||
|
||||
const ptr = PullToRefresh.init({
|
||||
mainElement: 'body',
|
||||
onRefresh() {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { getComponent } from '@symfony/ux-live-component';
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
static targets = ['download']
|
||||
static targets = ['download', 'deleteFileInput']
|
||||
|
||||
async initialize() {
|
||||
this.component = await getComponent(this.element);
|
||||
@@ -42,7 +42,8 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
deleteDownload(data) {
|
||||
fetch(`/api/download/${data.params.id}`, {method: 'DELETE'})
|
||||
const deleteFileInput = document.querySelector(`#delete_file_${data.params.id}`)
|
||||
fetch(`/api/download/${data.params.id}?deleteFile=${deleteFileInput.checked}`, {method: 'DELETE'})
|
||||
.then(res => res.json())
|
||||
.then(json => console.debug(json));
|
||||
}
|
||||
|
||||
@@ -64,4 +64,7 @@ return [
|
||||
'version' => '2.4.3',
|
||||
'type' => 'css',
|
||||
],
|
||||
'pulltorefreshjs' => [
|
||||
'version' => '0.1.22',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Base\Service;
|
||||
use Aimeos\Map;
|
||||
use App\Download\Framework\Entity\Download;
|
||||
use Nihilarr\PTN;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
@@ -21,6 +22,7 @@ class MediaFiles
|
||||
private string $tvShowsPath;
|
||||
|
||||
private Filesystem $filesystem;
|
||||
private LoggerInterface $logger;
|
||||
|
||||
public function __construct(
|
||||
#[Autowire(param: 'media.base_path')]
|
||||
@@ -33,12 +35,14 @@ class MediaFiles
|
||||
string $tvShowsPath,
|
||||
|
||||
Filesystem $filesystem,
|
||||
LoggerInterface $logger,
|
||||
) {
|
||||
$this->finder = new Finder();
|
||||
$this->basePath = $basePath;
|
||||
$this->moviesPath = $moviesPath;
|
||||
$this->tvShowsPath = $tvShowsPath;
|
||||
$this->filesystem = $filesystem;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function getPathByType(string $mediaType): string
|
||||
@@ -220,4 +224,26 @@ class MediaFiles
|
||||
{
|
||||
$this->filesystem->chmod($filepath, $permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filepath
|
||||
* @return bool
|
||||
* Returns true if file was deleted
|
||||
* Returns false is file not found or was not deleted
|
||||
*/
|
||||
public function removeFile(string $filepath): bool
|
||||
{
|
||||
if (true === $this->filesystem->exists($filepath)) {
|
||||
try {
|
||||
$this->filesystem->remove($filepath);
|
||||
return true;
|
||||
} catch (\Throwable $exception) {
|
||||
$this->logger->error($exception->getMessage(), ['file' => $filepath]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->logger->warning('> [MediaFiles] Attempted to remove file, but it did not exist.', ['file' => $filepath]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,5 +11,6 @@ class DeleteDownloadCommand implements CommandInterface
|
||||
{
|
||||
public function __construct(
|
||||
public int $downloadId,
|
||||
public bool $deleteFile = false,
|
||||
) {}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ namespace App\Download\Action\Handler;
|
||||
use App\Download\Action\Command\DeleteDownloadCommand;
|
||||
use App\Download\Action\Result\DeleteDownloadResult;
|
||||
use App\Download\Framework\Repository\DownloadRepository;
|
||||
use App\Library\Action\Command\DeleteMediaFileCommand;
|
||||
use App\Library\Action\Handler\DeleteMediaFileHandler;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
@@ -14,13 +16,26 @@ readonly class DeleteDownloadHandler implements HandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private DownloadRepository $downloadRepository,
|
||||
private DeleteMediaFileHandler $deleteMediaFileHandler,
|
||||
) {}
|
||||
|
||||
public function handle(CommandInterface $command): ResultInterface
|
||||
{
|
||||
$download = $this->downloadRepository->find($command->downloadId);
|
||||
|
||||
if (true === $command->deleteFile) {
|
||||
$deletedFileResult = $this->deleteMediaFileHandler->handle(new DeleteMediaFileCommand(
|
||||
filename: $download->getFilename(),
|
||||
downloadId: $command->downloadId
|
||||
));
|
||||
}
|
||||
$this->downloadRepository->delete($command->downloadId);
|
||||
|
||||
return new DeleteDownloadResult(200, 'Success', $download);
|
||||
return new DeleteDownloadResult(
|
||||
status: 200,
|
||||
message: 'Success',
|
||||
download: $download,
|
||||
deleteMediaFileResult: $deletedFileResult ?? null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Download\Action\Input;
|
||||
|
||||
use App\Download\Action\Command\DeleteDownloadCommand;
|
||||
use OneToMany\RichBundle\Attribute\SourceQuery;
|
||||
use OneToMany\RichBundle\Attribute\SourceRoute;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\InputInterface;
|
||||
@@ -13,12 +14,15 @@ class DeleteDownloadInput implements InputInterface
|
||||
public function __construct(
|
||||
#[SourceRoute('downloadId')]
|
||||
public int $downloadId,
|
||||
#[SourceQuery('deleteFile')]
|
||||
public bool $deleteFile = false,
|
||||
) {}
|
||||
|
||||
public function toCommand(): CommandInterface
|
||||
{
|
||||
return new DeleteDownloadCommand(
|
||||
$this->downloadId,
|
||||
$this->deleteFile,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -12,5 +12,6 @@ class DeleteDownloadResult implements ResultInterface
|
||||
public int $status,
|
||||
public string $message,
|
||||
public Download $download,
|
||||
public ?DeleteMediaFileResult $deleteMediaFileResult = null,
|
||||
) {}
|
||||
}
|
||||
|
||||
14
src/Download/Action/Result/DeleteMediaFileResult.php
Normal file
14
src/Download/Action/Result/DeleteMediaFileResult.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Action\Result;
|
||||
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
|
||||
class DeleteMediaFileResult implements ResultInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $message,
|
||||
public string $filepath,
|
||||
public bool $isDeleted,
|
||||
) {}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ class ApiController extends AbstractController
|
||||
message: "{$result->download->getTitle()} has been deleted.",
|
||||
);
|
||||
|
||||
return $this->json(['status' => 200, 'message' => 'Download Deleted']);
|
||||
return $this->json($result);
|
||||
}
|
||||
|
||||
#[Route('/api/download/{downloadId}/pause', name: 'api_download_pause', methods: ['PATCH'])]
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\User\Framework\Entity\User;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Gedmo\Timestampable\Traits\TimestampableEntity;
|
||||
use Nihilarr\PTN;
|
||||
use Symfony\Component\Serializer\Attribute\Ignore;
|
||||
use Symfony\UX\Turbo\Attribute\Broadcast;
|
||||
|
||||
#[ORM\Entity(repositoryClass: DownloadRepository::class)]
|
||||
@@ -44,6 +45,7 @@ class Download
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $episodeId = null;
|
||||
|
||||
#[Ignore]
|
||||
#[ORM\ManyToOne(inversedBy: 'downloads')]
|
||||
private ?User $user = null;
|
||||
|
||||
|
||||
16
src/Library/Action/Command/DeleteMediaFileCommand.php
Normal file
16
src/Library/Action/Command/DeleteMediaFileCommand.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library\Action\Command;
|
||||
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
|
||||
/**
|
||||
* @implements CommandInterface<DeleteMediaFileCommand>
|
||||
*/
|
||||
class DeleteMediaFileCommand implements CommandInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $filename,
|
||||
public ?int $downloadId = null,
|
||||
) {}
|
||||
}
|
||||
42
src/Library/Action/Handler/DeleteMediaFileHandler.php
Normal file
42
src/Library/Action/Handler/DeleteMediaFileHandler.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library\Action\Handler;
|
||||
|
||||
use App\Base\Service\MediaFiles;
|
||||
use App\Download\Action\Result\DeleteMediaFileResult;
|
||||
use App\Download\Framework\Entity\Download;
|
||||
use App\Download\Framework\Repository\DownloadRepository;
|
||||
use App\Library\Action\Command\DeleteMediaFileCommand;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
|
||||
/**
|
||||
* @implements HandlerInterface<DeleteMediaFileCommand,DeleteMediaFileResult>
|
||||
*/
|
||||
class DeleteMediaFileHandler implements HandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DownloadRepository $downloadRepository,
|
||||
private readonly MediaFiles $mediaFiles,
|
||||
) {}
|
||||
|
||||
public function handle(CommandInterface $command): ResultInterface
|
||||
{
|
||||
/** @var Download $downloadRecord */
|
||||
$downloadRecord = $this->downloadRepository->find($command->downloadId);
|
||||
$filepath = $this->getFullFilepath($downloadRecord);
|
||||
$result = $this->mediaFiles->removeFile($filepath);
|
||||
|
||||
return new DeleteMediaFileResult(
|
||||
message: true === $result ? 'File removed' : 'File not removed',
|
||||
filepath: $filepath,
|
||||
isDeleted: $result
|
||||
);
|
||||
}
|
||||
|
||||
private function getFullFilepath(Download $download): string
|
||||
{
|
||||
return $this->mediaFiles->getPathByType($download->getMediaType()) . DIRECTORY_SEPARATOR . $download->getFilename();
|
||||
}
|
||||
}
|
||||
29
src/Library/Action/Input/DeleteMediaFileInput.php
Normal file
29
src/Library/Action/Input/DeleteMediaFileInput.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library\Action\Input;
|
||||
|
||||
use App\Library\Action\Command\DeleteMediaFileCommand;
|
||||
use OneToMany\RichBundle\Attribute\SourceRequest;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\InputInterface;
|
||||
|
||||
/**
|
||||
* @implements InputInterface<DeleteMediaFileInput,DeleteMediaFileCommand>
|
||||
*/
|
||||
class DeleteMediaFileInput implements InputInterface
|
||||
{
|
||||
public function __construct(
|
||||
#[SourceRequest('filename')]
|
||||
public string $filename,
|
||||
#[SourceRequest('downloadId', nullify: true)]
|
||||
public ?int $downloadId = null,
|
||||
) {}
|
||||
|
||||
public function toCommand(): CommandInterface
|
||||
{
|
||||
return new DeleteMediaFileCommand(
|
||||
$this->filename,
|
||||
$this->downloadId,
|
||||
);
|
||||
}
|
||||
}
|
||||
15
src/Library/Action/Result/DeleteMediaFileResult.php
Normal file
15
src/Library/Action/Result/DeleteMediaFileResult.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library\Action\Result;
|
||||
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
|
||||
/**
|
||||
* @implements ResultInterface
|
||||
*/
|
||||
class DeleteMediaFileResult implements ResultInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $status,
|
||||
) {}
|
||||
}
|
||||
213
src/Search/Framework/Command/SearchCommand.php
Normal file
213
src/Search/Framework/Command/SearchCommand.php
Normal file
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
namespace App\Search\Framework\Command;
|
||||
|
||||
use Aimeos\Map;
|
||||
use App\Download\Action\Command\DownloadMediaCommand;
|
||||
use App\Download\Action\Handler\DownloadMediaHandler;
|
||||
use App\Download\Framework\Repository\DownloadRepository;
|
||||
use App\Search\Action\Handler\SearchHandler;
|
||||
use App\Search\Action\Command\SearchCommand as CommandInput;
|
||||
use App\Search\Action\Result\SearchResult;
|
||||
use App\Tmdb\TmdbResult;
|
||||
use App\Torrentio\Action\Command\GetMovieOptionsCommand;
|
||||
use App\Torrentio\Action\Command\GetTvShowOptionsCommand;
|
||||
use App\Torrentio\Action\Handler\GetMovieOptionsHandler;
|
||||
use App\Torrentio\Action\Handler\GetTvShowOptionsHandler;
|
||||
use App\Torrentio\Result\TorrentioResult;
|
||||
use App\User\Framework\Repository\UserRepository;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
#[AsCommand('search')]
|
||||
class SearchCommand extends Command
|
||||
{
|
||||
private SearchHandler $searchHandler;
|
||||
private GetTvShowOptionsHandler $getTvShowOptionsHandler;
|
||||
private GetMovieOptionsHandler $getMovieOptionsHandler;
|
||||
private UserRepository $userRepository;
|
||||
private DownloadRepository $downloadRepository;
|
||||
private DownloadMediaHandler $downloadMediaHandler;
|
||||
private MessageBusInterface $bus;
|
||||
|
||||
public function __construct(SearchHandler $searchHandler, GetTvShowOptionsHandler $getTvShowOptionsHandler,
|
||||
GetMovieOptionsHandler $getMovieOptionsHandler,
|
||||
UserRepository $userRepository,
|
||||
DownloadRepository $downloadRepository,
|
||||
DownloadMediaHandler $downloadMediaHandler,
|
||||
MessageBusInterface $bus,
|
||||
?string $name = null
|
||||
) {
|
||||
parent::__construct($name);
|
||||
$this->searchHandler = $searchHandler;
|
||||
$this->getTvShowOptionsHandler = $getTvShowOptionsHandler;
|
||||
$this->getMovieOptionsHandler = $getMovieOptionsHandler;
|
||||
$this->userRepository = $userRepository;
|
||||
$this->downloadRepository = $downloadRepository;
|
||||
$this->downloadMediaHandler = $downloadMediaHandler;
|
||||
$this->bus = $bus;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->addArgument('term', InputArgument::REQUIRED);
|
||||
$this->addOption('local', 'l', InputOption::VALUE_NONE, 'Perform the download locally instead of submitting it to the queue.');
|
||||
}
|
||||
|
||||
public function run(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$command = new CommandInput($input->getArgument('term'));
|
||||
|
||||
// Perform search
|
||||
$mediaOptions = $this->searchHandler->handle($command);
|
||||
|
||||
// Render results and ask the User to pick one
|
||||
$mediaChoice = $this->askToChooseMediaOption($io, $output, $mediaOptions);
|
||||
|
||||
// Find download options based on the User's choice
|
||||
$downloadOptions = $this->fetchDownloadOptions($mediaChoice);
|
||||
|
||||
// Render results and ask the User to pick one
|
||||
$downloadChoice = $this->askToChooseDownloadOption($io, $output, $downloadOptions);
|
||||
|
||||
// Have user confirm download action
|
||||
$confirmation = $this->askToConfirmDownload($io, $output, $downloadChoice);
|
||||
|
||||
// Begin download or submit to the queue
|
||||
if (true === $confirmation) {
|
||||
$downloadLocally = $input->getOption('local');
|
||||
$this->submitDownload($io, $mediaChoice, $downloadChoice, $downloadLocally);
|
||||
} else {
|
||||
$io->success('No results found.');
|
||||
}
|
||||
|
||||
$io->success('Success!');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function askToChooseMediaOption(SymfonyStyle $io, OutputInterface $output, SearchResult $result): TmdbResult
|
||||
{
|
||||
$table = new Table($output)
|
||||
->setHeaders(['ID', 'Title', 'Year', 'IMDb ID', 'Description'])
|
||||
->setRows(
|
||||
Map::from($result->results)
|
||||
->map(fn ($result, $index) => [$index, $result->title, $result->year, $result->imdbId, substr($result->description, 0, 80) . '...'])
|
||||
->toArray()
|
||||
);
|
||||
|
||||
$table->render();
|
||||
|
||||
$question = new Question('Enter the ID of the correct result: ');
|
||||
$choiceId = $io->askQuestion($question);
|
||||
$choice = $result->results[$choiceId];
|
||||
|
||||
$io->info('You chose: ' . $choice->title);
|
||||
$io->table(
|
||||
['ID', 'Title', 'Year', 'IMDb ID', 'Description'],
|
||||
[
|
||||
[$choiceId, $choice->title, $choice->year, $choice->imdbId, substr($choice->description, 0, 80) . '...'],
|
||||
]
|
||||
);
|
||||
|
||||
return $choice;
|
||||
}
|
||||
|
||||
private function fetchDownloadOptions(TmdbResult $result): array
|
||||
{
|
||||
$handlers = [
|
||||
'movies' => $this->getMovieOptionsHandler,
|
||||
'tvshows' => $this->getTvShowOptionsHandler,
|
||||
];
|
||||
|
||||
$handler = $handlers[$result->mediaType];
|
||||
|
||||
if ("movies" === $result->mediaType) {
|
||||
$command = new GetMovieOptionsCommand(
|
||||
$result->tmdbId,
|
||||
$result->imdbId
|
||||
);
|
||||
} else {
|
||||
$command = new GetTvShowOptionsCommand(
|
||||
$result->tmdbId,
|
||||
$result->imdbId,
|
||||
1,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
$result = $handler->handle($command);
|
||||
return $result->results;
|
||||
}
|
||||
|
||||
private function askToChooseDownloadOption(SymfonyStyle $io, OutputInterface $output, array $options): TorrentioResult
|
||||
{
|
||||
$table = new Table($output)
|
||||
->setHeaders(['ID', 'Size', 'Resolution', 'Codec', 'Seeders', 'Provider', 'Language'])
|
||||
->setRows(
|
||||
Map::from($options)
|
||||
->map(fn (TorrentioResult $result, $index) => [$index, $result->size, $result->resolution, $result->codec, $result->seeders, $result->provider, implode(', ', $result->languages)])
|
||||
->toArray()
|
||||
);
|
||||
$table->render();
|
||||
|
||||
$question = new Question('Enter the ID of the item to download: ');
|
||||
$choiceId = $io->askQuestion($question);
|
||||
$choice = $options[$choiceId];
|
||||
$io->info('You chose: ' . $choice->title);
|
||||
|
||||
return $choice;
|
||||
}
|
||||
|
||||
private function askToConfirmDownload(SymfonyStyle $io, OutputInterface $output, TorrentioResult $downloadOption): bool
|
||||
{
|
||||
$question = new ChoiceQuestion('Are you sure you want to download the above file?', ['yes', 'no']);
|
||||
$choice = $io->askQuestion($question);
|
||||
return $choice === 'yes';
|
||||
}
|
||||
|
||||
private function submitDownload(SymfonyStyle $io, TmdbResult $mediaChoice, TorrentioResult $downloadOption, bool $downloadLocally = false): void
|
||||
{
|
||||
$io->writeln('> Adding download record');
|
||||
$user = $this->userRepository->find(1);
|
||||
$download = $this->downloadRepository->insert(
|
||||
$user,
|
||||
$downloadOption->url,
|
||||
$downloadOption->title,
|
||||
$downloadOption->filename,
|
||||
$mediaChoice->imdbId,
|
||||
$mediaChoice->mediaType,
|
||||
);
|
||||
|
||||
$io->writeln('> Download record added: ' . $download->getId());
|
||||
$downloadCommand = new DownloadMediaCommand(
|
||||
$download->getUrl(),
|
||||
$download->getTitle(),
|
||||
$download->getFilename(),
|
||||
$download->getMEdiaType(),
|
||||
$download->getImdbId(),
|
||||
$download->getUser()->getId(),
|
||||
$download->getId()
|
||||
);
|
||||
|
||||
if (true === $downloadLocally) {
|
||||
$io->writeln('> Beginning local download');
|
||||
$this->downloadMediaHandler->handle($downloadCommand);
|
||||
} else {
|
||||
$io->writeln('> Submitting download to queue');
|
||||
$this->bus->dispatch($downloadCommand);
|
||||
}
|
||||
|
||||
$io->writeln('> Download added to queue');
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Torrentio\Action\Handler;
|
||||
|
||||
use App\Base\Service\MediaFiles;
|
||||
use App\Library\Dto\MediaFileDto;
|
||||
use App\Tmdb\Tmdb;
|
||||
use App\Torrentio\Action\Command\GetTvShowOptionsCommand;
|
||||
use App\Torrentio\Action\Result\GetTvShowOptionsResult;
|
||||
@@ -28,7 +29,7 @@ class GetTvShowOptionsHandler implements HandlerInterface
|
||||
|
||||
return new GetTvShowOptionsResult(
|
||||
media: $media,
|
||||
file: $file,
|
||||
file: MediaFileDto::fromSplFileInfo($file),
|
||||
season: $command->season,
|
||||
episode: $command->episode,
|
||||
results: $this->torrentio->fetchEpisodeResults(
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
namespace App\Torrentio\Action\Result;
|
||||
|
||||
use App\Library\Dto\MediaFileDto;
|
||||
use App\Tmdb\TmdbResult;
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
use Symfony\Component\Finder\SplFileInfo;
|
||||
|
||||
class GetTvShowOptionsResult implements ResultInterface
|
||||
{
|
||||
public function __construct(
|
||||
public TmdbResult $media,
|
||||
public bool|SplFileInfo $file,
|
||||
public bool|MediaFileDto $file,
|
||||
public string $season,
|
||||
public string $episode,
|
||||
public array $results
|
||||
|
||||
@@ -13,6 +13,7 @@ use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Attribute\Cache;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
@@ -26,6 +27,7 @@ final class WebController extends AbstractController
|
||||
private readonly Broadcaster $broadcaster,
|
||||
) {}
|
||||
|
||||
#[Cache(expires: 3600, public: false, mustRevalidate: true)]
|
||||
#[Route('/torrentio/movies/{tmdbId}/{imdbId}', name: 'app_torrentio_movies')]
|
||||
public function movieOptions(GetMovieOptionsInput $input, CacheInterface $cache, Request $request): Response
|
||||
{
|
||||
@@ -35,18 +37,18 @@ final class WebController extends AbstractController
|
||||
$input->imdbId
|
||||
);
|
||||
|
||||
return $cache->get($cacheId, function (ItemInterface $item) use ($input, $request) {
|
||||
$results = $cache->get($cacheId, function (ItemInterface $item) use ($input, $request) {
|
||||
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
||||
$results = $this->getMovieOptionsHandler->handle($input->toCommand());
|
||||
|
||||
if ($request->headers->get('Turbo-Frame')) {
|
||||
return $this->sendFragmentResponse($results, $request);
|
||||
}
|
||||
|
||||
return $this->render('torrentio/movies.html.twig', [
|
||||
'results' => $results,
|
||||
]);
|
||||
return $this->getMovieOptionsHandler->handle($input->toCommand());
|
||||
});
|
||||
|
||||
if ($request->headers->get('Turbo-Frame')) {
|
||||
return $this->sendFragmentResponse($results, $request);
|
||||
}
|
||||
|
||||
return $this->render('torrentio/movies.html.twig', [
|
||||
'results' => $results,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/torrentio/tvshows/{tmdbId}/{imdbId}/{season?}/{episode?}', name: 'app_torrentio_tvshows')]
|
||||
@@ -61,18 +63,18 @@ final class WebController extends AbstractController
|
||||
);
|
||||
|
||||
try {
|
||||
return $cache->get($cacheId, function (ItemInterface $item) use ($input, $request) {
|
||||
$results = $cache->get($cacheId, function (ItemInterface $item) use ($input) {
|
||||
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
||||
$results = $this->getTvShowOptionsHandler->handle($input->toCommand());
|
||||
|
||||
if ($request->headers->get('Turbo-Frame')) {
|
||||
return $this->sendFragmentResponse($results, $request);
|
||||
}
|
||||
|
||||
return $this->render('torrentio/tvshows.html.twig', [
|
||||
'results' => $results,
|
||||
]);
|
||||
return $this->getTvShowOptionsHandler->handle($input->toCommand());
|
||||
});
|
||||
|
||||
if ($request->headers->get('Turbo-Frame')) {
|
||||
return $this->sendFragmentResponse($results, $request);
|
||||
}
|
||||
|
||||
return $this->render('torrentio/tvshows.html.twig', [
|
||||
'results' => $results,
|
||||
]);
|
||||
} catch (TorrentioRateLimitException $exception) {
|
||||
$this->broadcaster->alert('Warning', 'Torrentio has rate limited your requests. Please wait a few minutes before trying again.', 'warning');
|
||||
return $this->render('bare.html.twig',
|
||||
|
||||
@@ -44,7 +44,12 @@
|
||||
|
||||
{% set delete_button = component('ux:icon', {name: 'ic:twotone-cancel', height: '17.75px', width: '17.75px', class: 'rounded-full align-middle text-red-600 hover:text-red-700' }) %}
|
||||
<twig:Modal heading="But wait!" button_text="{{ delete_button }}" submit_action="{{ stimulus_action('download_list', 'deleteDownload', 'click', {id: download.id}) }}" show_cancel show_submit>
|
||||
Are you sure you want to delete <span class="font-bold">{{ download.filename }}</span>?
|
||||
<p class="mb-1">Are you sure you want to delete the following record?</p>
|
||||
<p class="mb-1 ml-4 italic">{{ download.filename }}</p>
|
||||
<div class="">
|
||||
<input id="delete_file_{{ download.id }}" class="accent-orange-500" type="checkbox" value="false" name="delete_file" />
|
||||
<label for="delete_file_{{ download.id }}">Delete the file as well?</label>
|
||||
</div>
|
||||
</twig:Modal>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -13,7 +13,9 @@
|
||||
{% block tvshow_results %}
|
||||
<turbo-stream action="replace" targets="#{{ target }}">
|
||||
<template>
|
||||
{{ include('torrentio/partial/option-table.html.twig', {controller: 'tv-results'}) }}
|
||||
<div id="{{ target }}">
|
||||
{{ include('torrentio/partial/option-table.html.twig', {controller: 'tv-results'}) }}
|
||||
</div>
|
||||
</template>
|
||||
</turbo-stream>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user