diff --git a/src/Base/Service/MediaFiles.php b/src/Base/Service/MediaFiles.php
index bdb16d4..a99eca5 100644
--- a/src/Base/Service/MediaFiles.php
+++ b/src/Base/Service/MediaFiles.php
@@ -168,6 +168,37 @@ class MediaFiles
return false;
}
+ /**
+ * @param string $tvshowTitle
+ * @return array|false
+ */
+ public function tvshowExists(string $tvshowTitle): Map|false
+ {
+ $existingEpisodes = $this->getEpisodes($tvshowTitle, false);
+
+ if ($existingEpisodes->isEmpty()) {
+ return false;
+ }
+
+ $episodes = new Map;
+ /** @var SplFileInfo $episode */
+ foreach ($existingEpisodes as $episode) {
+ $ptn = (object) (new PTN())->parse($episode->getFilename());
+
+ if (!property_exists($ptn, 'season') || !property_exists($ptn, 'episode')) {
+ continue;
+ }
+
+ $episodes->push($episode);
+ }
+
+ if ($episodes->count() > 0) {
+ return $episodes;
+ }
+
+ return false;
+ }
+
public function movieExists(string $title)
{
$filepath = $this->moviesPath . DIRECTORY_SEPARATOR . $title;
diff --git a/src/Library/Action/Command/GetMediaFromLibraryCommand.php b/src/Library/Action/Command/GetMediaFromLibraryCommand.php
new file mode 100644
index 0000000..d69f858
--- /dev/null
+++ b/src/Library/Action/Command/GetMediaFromLibraryCommand.php
@@ -0,0 +1,20 @@
+
+ */
+class GetMediaFromLibraryCommand implements CommandInterface
+{
+ public function __construct(
+ public ?int $userId = null,
+ public ?string $mediaType = null,
+ public ?string $imdbId = null,
+ public ?string $title = null,
+ public ?string $season = null,
+ public ?string $episode = null,
+ ) {}
+}
diff --git a/src/Library/Action/Handler/GetMediaInfoFromLibraryHandler.php b/src/Library/Action/Handler/GetMediaInfoFromLibraryHandler.php
new file mode 100644
index 0000000..ee8354f
--- /dev/null
+++ b/src/Library/Action/Handler/GetMediaInfoFromLibraryHandler.php
@@ -0,0 +1,169 @@
+
+ */
+class GetMediaInfoFromLibraryHandler implements HandlerInterface
+{
+ public function __construct(
+ private readonly TmdbClient $tmdb,
+ private readonly MediaFiles $mediaFiles,
+ private readonly LoggerInterface $logger,
+ private readonly MonitorRepository $monitorRepository,
+ ) {}
+
+ public function handle(C $command): R
+ {
+ $result = new GetMediaFromLibraryResult();
+ $tmdbResult = $this->fetchTmdbData($command->imdbId, $command->mediaType);
+
+ if (null === $tmdbResult) {
+ $this->logger->warning('[GetMediaInfoFromLibraryHandler] TMDb result was not found, this may lead to issues in the rest of the library search', (array) $command);
+ }
+
+ $this->setResultExists($tmdbResult->mediaType, $tmdbResult->title, $result);
+ if ($result->notExists()) {
+ return $result;
+ }
+
+ $this->parseFromTmdbResult($tmdbResult, $result);
+
+ if ($command->mediaType === MediaType::TvShow->value) {
+ $this->setEpisodes($tmdbResult, $result);
+ $this->setSeasons($tmdbResult, $result);
+ $this->setMonitors($command->userId, $command->imdbId, $result);
+ }
+
+ return $result;
+ }
+
+ private function fetchTmdbData(string $imdbId, string $mediaType): ?TmdbResult
+ {
+ return match($mediaType) {
+ MediaType::Movie->value => $this->tmdb->movieDetails($imdbId),
+ MediaType::TvShow->value => $this->tmdb->tvShowDetails($imdbId),
+ default => null,
+ };
+ }
+
+ private function setResultExists(string $mediaType, string $title, GetMediaFromLibraryResult $result): void
+ {
+ $fsResult = match($mediaType) {
+ MediaType::Movie->value => $this->mediaFiles->movieExists($title),
+ MediaType::TvShow->value => $this->mediaFiles->tvShowExists($title),
+ default => false,
+ };
+ if (false === $fsResult) {
+ $result->setExists(false);
+ } else {
+ $result->setExists(true);
+ }
+ }
+
+ public function parseFromTmdbResult(TmdbResult $tmdbResult, GetMediaFromLibraryResult $result): void
+ {
+ $result->setTitle($tmdbResult->title);
+ $result->setMediaType($tmdbResult->mediaType);
+ $result->setImdbId($tmdbResult->imdbId);
+ }
+
+ public function setEpisodes(TmdbResult $tmdbResult, GetMediaFromLibraryResult $result): void
+ {
+ $existingEpisodeFiles = $this->mediaFiles->tvshowExists($tmdbResult->title);
+
+ $existingEpisodeMap = [];
+ foreach ($existingEpisodeFiles as $file) {
+ /** @var \SplFileInfo $file */
+ $ptn = (object) new PTN()->parse($file->getBasename());
+
+ if (!array_key_exists($ptn->season, $existingEpisodeMap)) {
+ $existingEpisodeMap[$ptn->season] = [];
+ }
+
+ if (!in_array($ptn->episode, $existingEpisodeMap[$ptn->season])) {
+ $existingEpisodeMap[$ptn->season][] = $ptn->episode;
+ }
+ }
+
+ $existingEpisodes = [];
+ $missingEpisodes = [];
+ foreach ($tmdbResult->episodes as $season => $episodes) {
+ foreach ($episodes as $episode) {
+ if (array_key_exists($season, $existingEpisodeMap)) {
+ if (in_array($episode->episodeNumber, $existingEpisodeMap[$season])) {
+ $existingEpisodes[] = $episode;
+ } else {
+ $missingEpisodes[] = $episode;
+ }
+ } else {
+ $missingEpisodes[] = $episode;
+ }
+ }
+ }
+ $result->setEpisodes($existingEpisodes);
+ $result->setMissingEpisodes($missingEpisodes);
+ }
+
+ public function setSeasons(TmdbResult $tmdbResult, GetMediaFromLibraryResult $result): void
+ {
+ $existingEpisodeFiles = $this->mediaFiles->tvshowExists($tmdbResult->title);
+
+ $existingEpisodeMap = [];
+ foreach ($existingEpisodeFiles as $file) {
+ /** @var \SplFileInfo $file */
+ $ptn = (object) new PTN()->parse($file->getBasename());
+ $existingEpisodeMap[$ptn->season][] = $ptn->episode;
+ }
+
+ $existingFullSeasons = [];
+ $existingPartialSeasons = [];
+ $missingSeasons = [];
+ foreach ($existingEpisodeMap as $season => $episodes) {
+ if (count($tmdbResult->episodes[$season]) === count($episodes)) {
+ $existingFullSeasons[] = $season;
+ } elseif (count($episodes) > 0) {
+ $existingPartialSeasons[] = $season;
+ }
+ }
+
+ $seasons = array_keys($tmdbResult->episodes);
+ foreach ($seasons as $season) {
+ if (!in_array($season, $existingFullSeasons) && !in_array($season, $existingPartialSeasons)) {
+ $missingSeasons[] = $season;
+ }
+ }
+
+ $result->setSeasons($existingFullSeasons);
+ $result->setPartialSeasons($existingPartialSeasons);
+ $result->setMissingSeasons($missingSeasons);
+ }
+
+ public function setMonitors(int $userId, string $imdbId, GetMediaFromLibraryResult $result)
+ {
+ $result->setMonitorCount(
+ $this->monitorRepository->countUserChildrenByParentId($userId, $imdbId)
+ );
+ $result->setActiveMonitorCount(
+ $this->monitorRepository->countUserActiveChildrenByParentId($userId, $imdbId)
+ );
+ $result->setCompleteMonitorCount(
+ $this->monitorRepository->countUserCompleteChildrenByParentId($userId, $imdbId)
+ );
+ }
+}
diff --git a/src/Library/Action/Input/GetMediaInfoFromLibraryInput.php b/src/Library/Action/Input/GetMediaInfoFromLibraryInput.php
new file mode 100644
index 0000000..a91f06c
--- /dev/null
+++ b/src/Library/Action/Input/GetMediaInfoFromLibraryInput.php
@@ -0,0 +1,39 @@
+
+ */
+class GetMediaInfoFromLibraryInput implements InputInterface
+{
+ public function __construct(
+ #[SourceRequest('imdbId', nullify: true)]
+ public ?string $imdbId = null,
+ #[SourceRequest('title', nullify: true)]
+ public ?string $title = null,
+ #[SourceRequest('season', nullify: true)]
+ public ?string $season = null,
+ #[SourceRequest('episode', nullify: true)]
+ public ?string $episode = null,
+ ) {}
+
+ public function toCommand(): C
+ {
+ if (null === $this->imdbId && null === $this->title) {
+ throw new \InvalidArgumentException('Either imdbId or title must be set', 400);
+ }
+
+ return new GetMediaFromLibraryCommand(
+ imdbId: $this->imdbId,
+ title: $this->title,
+ season: $this->season,
+ episode: $this->episode,
+ );
+ }
+}
diff --git a/src/Library/Action/Result/GetMediaFromLibraryResult.php b/src/Library/Action/Result/GetMediaFromLibraryResult.php
new file mode 100644
index 0000000..2dca198
--- /dev/null
+++ b/src/Library/Action/Result/GetMediaFromLibraryResult.php
@@ -0,0 +1,171 @@
+exists;
+ }
+
+ public function notExists(): bool
+ {
+ return !$this->exists;
+ }
+
+ public function setExists(bool $exists): void
+ {
+ $this->exists = $exists;
+ }
+
+ public function getTitle(): ?string
+ {
+ return $this->title;
+ }
+
+ public function setTitle(?string $title): void
+ {
+ $this->title = $title;
+ }
+
+ public function getImdbId(): ?string
+ {
+ return $this->imdbId;
+ }
+
+ public function setImdbId(?string $imdbId): void
+ {
+ $this->imdbId = $imdbId;
+ }
+
+ public function getMediaType(): ?string
+ {
+ return $this->mediaType;
+ }
+
+ public function setMediaType(?string $mediaType): void
+ {
+ $this->mediaType = $mediaType;
+ }
+
+ public function getEpisodes(): ?array
+ {
+ return $this->episodes;
+ }
+
+ public function setEpisodes(?array $episodes): void
+ {
+ $this->episodes = $episodes;
+ }
+
+ public function getEpisodeCount(): ?int
+ {
+ return count($this->episodes);
+ }
+
+ public function getMissingEpisodes(): ?array
+ {
+ return $this->missingEpisodes;
+ }
+
+ public function setMissingEpisodes(?array $missingEpisodes): void
+ {
+ $this->missingEpisodes = $missingEpisodes;
+ }
+
+ public function getMissingEpisodeCount(): ?int
+ {
+ return count($this->missingEpisodes);
+ }
+
+ public function getSeasons(): ?array
+ {
+ return $this->seasons;
+ }
+
+ public function setSeasons(?array $seasons): void
+ {
+ $this->seasons = $seasons;
+ }
+
+ public function getSeasonCount(): ?int
+ {
+ return count($this->seasons);
+ }
+
+ public function getPartialSeasons(): ?array
+ {
+ return $this->partialSeasons;
+ }
+
+ public function setPartialSeasons(?array $partialSeasons): void
+ {
+ $this->partialSeasons = $partialSeasons;
+ }
+
+ public function getPartialSeasonCount(): ?int
+ {
+ return count($this->partialSeasons);
+ }
+
+ public function getMissingSeasons(): ?array
+ {
+ return $this->missingSeasons;
+ }
+
+ public function setMissingSeasons(?array $missingSeasons): void
+ {
+ $this->missingSeasons = $missingSeasons;
+ }
+
+ public function getMissingSeasonCount(): ?int
+ {
+ return count($this->missingSeasons);
+ }
+
+ public function getMonitorCount(): ?int
+ {
+ return $this->monitorCount;
+ }
+
+ public function setMonitorCount(?int $monitorCount): void
+ {
+ $this->monitorCount = $monitorCount;
+ }
+
+ public function getActiveMonitorCount(): ?int
+ {
+ return $this->activeMonitorCount;
+ }
+
+ public function setActiveMonitorCount(?int $activeMonitorCount): void
+ {
+ $this->activeMonitorCount = $activeMonitorCount;
+ }
+
+ public function getCompleteMonitorCount(): ?int
+ {
+ return $this->completeMonitorCount;
+ }
+
+ public function setCompleteMonitorCount(?int $completeMonitorCount): void
+ {
+ $this->completeMonitorCount = $completeMonitorCount;
+ }
+}
diff --git a/src/Monitor/Framework/Controller/WebController.php b/src/Monitor/Framework/Controller/WebController.php
index 686a161..67fcc5b 100644
--- a/src/Monitor/Framework/Controller/WebController.php
+++ b/src/Monitor/Framework/Controller/WebController.php
@@ -3,6 +3,8 @@
namespace App\Monitor\Framework\Controller;
use App\Download\Action\Input\DeleteDownloadInput;
+use App\Library\Action\Command\GetMediaFromLibraryCommand;
+use App\Library\Action\Handler\GetMediaInfoFromLibraryHandler;
use App\Monitor\Action\Handler\AddMonitorHandler;
use App\Monitor\Action\Handler\DeleteMonitorHandler;
use App\Monitor\Action\Input\AddMonitorInput;
@@ -40,7 +42,7 @@ class WebController extends AbstractController
}
#[Route('/monitors/{id}', name: 'app.monitor.view', methods: ['GET'])]
- public function viewMonitor(Monitor $monitor, GetMediaInfoHandler $getMediaInfoHandler)
+ public function viewMonitor(Monitor $monitor, GetMediaInfoHandler $getMediaInfoHandler, GetMediaInfoFromLibraryHandler $handler)
{
$media = $getMediaInfoHandler->handle(
new GetMediaInfoCommand(
@@ -48,9 +50,19 @@ class WebController extends AbstractController
mediaType: 'tvshows',
)
);
+ $libraryResult = $handler->handle(
+ new GetMediaFromLibraryCommand(
+ $this->getUser()->getId(),
+ $media->media->mediaType,
+ $media->media->imdbId,
+ $media->media->title,
+ )
+ );
+
return $this->render('monitor/view.html.twig', [
'monitor' => $monitor,
'results' => $media,
+ 'library' => $libraryResult
]);
}
}
diff --git a/src/Monitor/Framework/Repository/MonitorRepository.php b/src/Monitor/Framework/Repository/MonitorRepository.php
index 64a068d..b6808a6 100644
--- a/src/Monitor/Framework/Repository/MonitorRepository.php
+++ b/src/Monitor/Framework/Repository/MonitorRepository.php
@@ -41,4 +41,83 @@ class MonitorRepository extends ServiceEntityRepository
->getQuery();
return $query->getResult();
}
+
+ public function getActiveUserMonitors()
+ {
+ return $this->asPaginator($this->monitorRepository->createQueryBuilder('m')
+ ->andWhere('m.status IN (:statuses)')
+ ->andWhere('(m.title LIKE :term OR m.imdbId LIKE :term OR m.monitorType LIKE :term OR m.status LIKE :term)')
+ ->andWhere('m.parent IS NULL')
+ ->setParameter('statuses', ['New', 'In Progress', 'Active'])
+ ->setParameter('term', '%'.$this->term.'%')
+ ->orderBy('m.id', 'DESC')
+ ->getQuery()
+ );
+ }
+
+ public function getChildMonitorsByParentId(int $parentId)
+ {
+ return $this->asPaginator(
+ $this->monitorRepository->createQueryBuilder('m')
+ ->andWhere("m.parent = :parentId")
+ ->setParameter('parentId', $parentId)
+ ->orderBy('m.id', 'DESC')
+ ->getQuery()
+ );
+ }
+
+ public function getCompleteUserMonitors()
+ {
+ return $this->asPaginator($this->monitorRepository->createQueryBuilder('m')
+ ->andWhere('m.status = :status')
+ ->andWhere('(m.title LIKE :term OR m.imdbId LIKE :term OR m.monitorType LIKE :term OR m.status LIKE :term)')
+ ->setParameter('status', 'Complete')
+ ->setParameter('term', '%'.$this->term.'%')
+ ->orderBy('m.id', 'DESC')
+ ->getQuery()
+ );
+ }
+
+ public function countUserChildrenByParentId(int $userId, string $imdbId): ?int
+ {
+ return $this->createQueryBuilder('m')
+ ->select('COUNT(m.id)')
+ ->andWhere('m.user = :user')
+ ->andWhere('m.imdbId = :imdbId')
+ ->setParameter('user', $userId)
+ ->setParameter('imdbId', $imdbId)
+ ->getQuery()
+ ->getSingleScalarResult()
+ ;
+ }
+
+ public function countUserActiveChildrenByParentId(int $userId, string $imdbId): ?int
+ {
+ return $this->createQueryBuilder('m')
+ ->select('COUNT(m.id)')
+ ->andWhere('m.user = :user')
+ ->andWhere('m.imdbId = :imdbId')
+ ->andWhere('m.status IN (:statuses)')
+ ->setParameter('user', $userId)
+ ->setParameter('statuses', ['Active', 'New', 'In Progress'])
+ ->setParameter('imdbId', $imdbId)
+ ->getQuery()
+ ->getSingleScalarResult()
+ ;
+ }
+
+ public function countUserCompleteChildrenByParentId(int $userId, string $imdbId): ?int
+ {
+ return $this->createQueryBuilder('m')
+ ->select('COUNT(m.id)')
+ ->andWhere('m.user = :user')
+ ->andWhere('m.imdbId = :imdbId')
+ ->andWhere('m.status IN (:statuses)')
+ ->setParameter('user', $userId)
+ ->setParameter('statuses', ['Complete'])
+ ->setParameter('imdbId', $imdbId)
+ ->getQuery()
+ ->getSingleScalarResult()
+ ;
+ }
}
diff --git a/templates/monitor/view.html.twig b/templates/monitor/view.html.twig
index 534098f..e5cb0d9 100644
--- a/templates/monitor/view.html.twig
+++ b/templates/monitor/view.html.twig
@@ -26,25 +26,34 @@
{{ results.media.description }}
-
- {% if results.media.stars != null %}
-
Starring: {{ results.media.stars|join(', ') }}
- {% endif %}
+
+
+ {% if results.media.stars != null %}
+ Starring: {{ results.media.stars|join(', ') }}
+ {% endif %}
- {% if results.media.directors != null %}
- Directors: {{ results.media.directors|join(', ') }}
- {% endif %}
+ {% if results.media.directors != null %}
+ Directors: {{ results.media.directors|join(', ') }}
+ {% endif %}
- {% if results.media.producers != null %}
- Producers: {{ results.media.producers|join(', ') }}
- {% endif %}
+ {% if results.media.producers != null %}
+ Producers: {{ results.media.producers|join(', ') }}
+ {% endif %}
- {% if results.media.creators != null %}
- Creators: {{ results.media.creators|join(', ') }}
- {% endif %}
+ {% if results.media.creators != null %}
+ Creators: {{ results.media.creators|join(', ') }}
+ {% endif %}
+
+
+
+ {% if results.media.premiereDate %}
+ Premiered: {{ results.media.premiereDate|date('n/j/Y', 'UTC') }}
+ {% endif %}
+
{% if results.media.genres != null %}
+{# Genres:
#}
{% for genre in results.media.genres %}
{{ genre }}
{% endfor %}
@@ -53,14 +62,55 @@
{% if results.media.mediaType == "tvshows" %}
-
-
- {{ results.media.numberSeasons }} season(s)
-
-
- {{ results.media.premiereDate|date(null, 'UTC') }}
-
+
+
+
In Your Library
+
+
+
Seasons
+
+
+ {{ library.seasonCount }} full
+
+
+ {{ library.partialSeasonCount }} partial
+
+
+ {{ library.missingSeasonCount }} missing
+
+
+
+
+
+
Episodes
+
+
+ {{ library.episodeCount }} existing
+
+
+ {{ library.missingEpisodeCount }} missing
+
+
+
+
+
+
Monitors
+
+
+ {{ library.monitorCount }} total
+
+
+ {{ library.activeMonitorCount }} active
+
+
+ {{ library.completeMonitorCount }} complete
+
+
+
+
+
+
{% endif %}
{% if "movies" == results.media.mediaType %}