feat: tv episode - existance indicator badge
This commit is contained in:
1
assets/icons/line-md/circle-twotone.svg
Normal file
1
assets/icons/line-md/circle-twotone.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" fill-opacity="0" stroke="currentColor" stroke-dasharray="64" stroke-dashoffset="64" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12c0 -4.97 4.03 -9 9 -9c4.97 0 9 4.03 9 9c0 4.97 -4.03 9 -9 9c-4.97 0 -9 -4.03 -9 -9Z"><animate fill="freeze" attributeName="fill-opacity" begin="0.6s" dur="0.15s" values="0;0.3"/><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.6s" values="64;0"/></path></svg>
|
||||||
|
After Width: | Height: | Size: 517 B |
@@ -18,6 +18,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- $PWD:/app
|
- $PWD:/app
|
||||||
|
- $PWD/var/download:/var/download
|
||||||
- mercure_data:/data
|
- mercure_data:/data
|
||||||
- mercure_config:/config
|
- mercure_config:/config
|
||||||
tty: true
|
tty: true
|
||||||
@@ -34,6 +35,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- $PWD:/app
|
- $PWD:/app
|
||||||
|
- $PWD/var/download:/var/download
|
||||||
tty: true
|
tty: true
|
||||||
command: php /app/bin/console messenger:consume async -vv --time-limit=3600 --limit=10
|
command: php /app/bin/console messenger:consume async -vv --time-limit=3600 --limit=10
|
||||||
|
|
||||||
|
|||||||
@@ -52,13 +52,13 @@ final class TorrentioController extends AbstractController
|
|||||||
$input->episode,
|
$input->episode,
|
||||||
);
|
);
|
||||||
|
|
||||||
return $cache->get($cacheId, function (ItemInterface $item) use ($input) {
|
// return $cache->get($cacheId, function (ItemInterface $item) use ($input) {
|
||||||
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
// $item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
||||||
$results = $this->getTvShowOptionsHandler->handle($input->toCommand());
|
$results = $this->getTvShowOptionsHandler->handle($input->toCommand());
|
||||||
return $this->render('torrentio/tvshows.html.twig', [
|
return $this->render('torrentio/tvshows.html.twig', [
|
||||||
'results' => $results,
|
'results' => $results,
|
||||||
]);
|
]);
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/torrentio/tvshows/clear/{tmdbId}/{imdbId}/{season?}/{episode?}', name: 'app_clear_torrentio_tvshows')]
|
#[Route('/torrentio/tvshows/clear/{tmdbId}/{imdbId}/{season?}/{episode?}', name: 'app_clear_torrentio_tvshows')]
|
||||||
|
|||||||
@@ -3,9 +3,11 @@
|
|||||||
namespace App\Monitor\Service;
|
namespace App\Monitor\Service;
|
||||||
|
|
||||||
use Aimeos\Map;
|
use Aimeos\Map;
|
||||||
|
use Nihilarr\PTN;
|
||||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
use Symfony\Component\Finder\Finder;
|
use Symfony\Component\Finder\Finder;
|
||||||
|
use Symfony\Component\Finder\SplFileInfo;
|
||||||
|
|
||||||
class MediaFiles
|
class MediaFiles
|
||||||
{
|
{
|
||||||
@@ -125,4 +127,24 @@ class MediaFiles
|
|||||||
|
|
||||||
return $path;
|
return $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function episodeExists(string $tvshowTitle, int $seasonNumber, int $episodeNumber)
|
||||||
|
{
|
||||||
|
$existingEpisodes = $this->getEpisodes($tvshowTitle, false);
|
||||||
|
|
||||||
|
if ($existingEpisodes->isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var SplFileInfo $episode */
|
||||||
|
foreach ($existingEpisodes as $episode) {
|
||||||
|
$ptn = (object) (new PTN())->parse($episode->getFilename());
|
||||||
|
|
||||||
|
if ($ptn->season === $seasonNumber && $ptn->episode === $episodeNumber) {
|
||||||
|
return $episode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Torrentio\Action\Handler;
|
namespace App\Torrentio\Action\Handler;
|
||||||
|
|
||||||
|
use App\Monitor\Service\MediaFiles;
|
||||||
use App\Tmdb\Tmdb;
|
use App\Tmdb\Tmdb;
|
||||||
use App\Torrentio\Action\Command\GetTvShowOptionsCommand;
|
use App\Torrentio\Action\Command\GetTvShowOptionsCommand;
|
||||||
use App\Torrentio\Action\Result\GetTvShowOptionsResult;
|
use App\Torrentio\Action\Result\GetTvShowOptionsResult;
|
||||||
@@ -16,12 +17,18 @@ class GetTvShowOptionsHandler implements HandlerInterface
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly Tmdb $tmdb,
|
private readonly Tmdb $tmdb,
|
||||||
private readonly Torrentio $torrentio,
|
private readonly Torrentio $torrentio,
|
||||||
|
private readonly MediaFiles $mediaFiles,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function handle(CommandInterface $command): ResultInterface
|
public function handle(CommandInterface $command): ResultInterface
|
||||||
{
|
{
|
||||||
|
$media = $this->tmdb->episodeDetails($command->tmdbId, $command->season, $command->episode);
|
||||||
|
$parentShow = $this->tmdb->mediaDetails($command->imdbId, 'tvshows');
|
||||||
|
$file = $this->mediaFiles->episodeExists($parentShow->title, $command->season, $command->episode);
|
||||||
|
|
||||||
return new GetTvShowOptionsResult(
|
return new GetTvShowOptionsResult(
|
||||||
media: $this->tmdb->episodeDetails($command->tmdbId, $command->season, $command->episode),
|
media: $media,
|
||||||
|
file: $file,
|
||||||
season: $command->season,
|
season: $command->season,
|
||||||
episode: $command->episode,
|
episode: $command->episode,
|
||||||
results: $this->torrentio->fetchEpisodeResults(
|
results: $this->torrentio->fetchEpisodeResults(
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ namespace App\Torrentio\Action\Result;
|
|||||||
|
|
||||||
use App\Tmdb\TmdbResult;
|
use App\Tmdb\TmdbResult;
|
||||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
|
use Symfony\Component\Finder\SplFileInfo;
|
||||||
|
|
||||||
class GetTvShowOptionsResult implements ResultInterface
|
class GetTvShowOptionsResult implements ResultInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public TmdbResult $media,
|
public TmdbResult $media,
|
||||||
|
public bool|SplFileInfo $file,
|
||||||
public string $season,
|
public string $season,
|
||||||
public string $episode,
|
public string $episode,
|
||||||
public array $results
|
public array $results
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ module.exports = {
|
|||||||
"bg-green-400",
|
"bg-green-400",
|
||||||
"bg-purple-400",
|
"bg-purple-400",
|
||||||
"bg-orange-400",
|
"bg-orange-400",
|
||||||
|
"bg-blue-600",
|
||||||
|
"bg-rose-600"
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
|
|||||||
@@ -8,23 +8,46 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="flex flex-col gap-4 grow">
|
<div class="flex flex-col gap-4 grow">
|
||||||
<h4 class="text-md font-bold">{{ results.episode }}. {{ results.media.title }}</h4>
|
<h4 class="text-md font-bold">
|
||||||
|
{{ results.episode }}. {{ results.media.title }}
|
||||||
|
</h4>
|
||||||
<p>{{ results.media.description }}</p>
|
<p>{{ results.media.description }}</p>
|
||||||
<div>
|
<div>
|
||||||
<small class="py-1 px-1.5 grow-0 font-bold bg-green-600 rounded-lg hover:cursor-pointer hover:bg-green-700 text-white"
|
<small class="py-1 px-1.5 mr-1 grow-0 font-bold bg-green-600 rounded-lg hover:cursor-pointer hover:bg-green-700 text-white"
|
||||||
{{ stimulus_action('tv-results', 'toggleList', 'click') }}
|
{{ stimulus_action('tv-results', 'toggleList', 'click') }}
|
||||||
><span {{ stimulus_target('tv-results', 'count') }}>{{ results.results|length }}</span> results</small>
|
>
|
||||||
<small class="py-1 px-1.5 grow-0 font-bold bg-gray-700 rounded-lg font-normal text-white" title="Air date {{ results.media.episodeAirDate }}"
|
<span {{ stimulus_target('tv-results', 'count') }}>{{ results.results|length }}</span> results
|
||||||
>{{ results.media.episodeAirDate }}</small>
|
</small>
|
||||||
|
|
||||||
|
{% if results.file != false %}
|
||||||
|
<small class="py-1 px-1.5 mr-1 grow-0 font-bold bg-blue-600 rounded-lg text-center text-white" title="{{ results.file.filename }}">
|
||||||
|
exists
|
||||||
|
</small>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if results.file == false %}
|
||||||
|
<small class="py-1 px-1.5 mr-1 grow-0 font-bold bg-rose-600 rounded-lg text-white" title="Episode has not been downloaded yet.">
|
||||||
|
missing
|
||||||
|
</small>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<small class="py-1 px-1.5 mr-1 grow-0 font-bold bg-gray-700 rounded-lg font-normal text-white" title="Air date {{ results.media.episodeAirDate }}">
|
||||||
|
{{ results.media.episodeAirDate }}
|
||||||
|
</small>
|
||||||
{# <small class="py-1 px-1.5 grow-0 font-bold bg-red-600 hover:bg-red-700 rounded-lg font-normal text-white cursor-pointer" title="Clear cache for {{ results.media.title }}"#}
|
{# <small class="py-1 px-1.5 grow-0 font-bold bg-red-600 hover:bg-red-700 rounded-lg font-normal text-white cursor-pointer" title="Clear cache for {{ results.media.title }}"#}
|
||||||
{# {{ stimulus_action('tv-results', 'clearCache', 'click') }}#}
|
{# {{ stimulus_action('tv-results', 'clearCache', 'click') }}#}
|
||||||
{# >Clear Cache</small>#}
|
{# >Clear Cache</small>#}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-4 justify-between">
|
<div class="flex flex-col gap-4 justify-between">
|
||||||
<input type="checkbox"
|
<div class="flex flex-col items-center">
|
||||||
{{ stimulus_target('tv-results', 'episodeSelector') }}
|
<input type="checkbox"
|
||||||
/>
|
{{ stimulus_target('tv-results', 'episodeSelector') }}
|
||||||
|
/>
|
||||||
|
<span title="You have this downloaded!">
|
||||||
|
<twig:ux:icon width="20" class="mt-2 text-green-600" name="line-md:circle-twotone" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div class="flex flex-col items-end hover:cursor-pointer"
|
<div class="flex flex-col items-end hover:cursor-pointer"
|
||||||
{{ stimulus_action('tv-results', 'toggleList', 'click') }}>
|
{{ stimulus_action('tv-results', 'toggleList', 'click') }}>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 32 32">
|
<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 32 32">
|
||||||
|
|||||||
Reference in New Issue
Block a user