Migrated Plex backend to use the new Custom exceptions.

This commit is contained in:
Abdulmhsen B. A. A
2023-12-17 00:08:47 +03:00
parent e893a8efcc
commit 88709e68ea
22 changed files with 181 additions and 77 deletions

View File

@@ -8,9 +8,9 @@ use App\Backends\Common\Context;
use App\Backends\Common\GuidInterface as iGuid;
use App\Backends\Plex\PlexClient;
use App\Libs\Entity\StateInterface as iState;
use App\Libs\Exceptions\Backends\InvalidArgumentException;
use App\Libs\Mappers\ImportInterface as iImport;
use App\Libs\Options;
use InvalidArgumentException;
use Psr\Http\Message\StreamInterface;
use Throwable;

View File

@@ -8,12 +8,12 @@ use App\Backends\Common\Context;
use App\Backends\Common\GuidInterface as iGuid;
use App\Backends\Plex\PlexClient;
use App\Libs\Container;
use App\Libs\Exceptions\Backends\InvalidArgumentException;
use App\Libs\Mappers\ImportInterface as iImport;
use App\Libs\Message;
use App\Libs\Options;
use App\Libs\QueueRequests;
use DateTimeInterface;
use InvalidArgumentException;
use Throwable;
final class Export extends Import

View File

@@ -15,7 +15,7 @@ final class GetIdentifier
{
use CommonTrait;
private string $action = 'unique identifier';
private string $action = 'plex.getIdentifier';
public function __construct(
protected HttpClientInterface $http,

View File

@@ -18,7 +18,7 @@ final class GetInfo
{
use CommonTrait;
private string $action = 'get info';
private string $action = 'plex.getInfo';
public function __construct(
protected HttpClientInterface $http,

View File

@@ -20,6 +20,8 @@ final class GetLibrariesList
{
use CommonTrait;
private string $action = 'plex.getLibrariesList';
public function __construct(protected HttpClientInterface $http, protected LoggerInterface $logger)
{
}
@@ -34,7 +36,7 @@ final class GetLibrariesList
*/
public function __invoke(Context $context, array $opts = []): Response
{
return $this->tryResponse(context: $context, fn: fn() => $this->action($context, $opts));
return $this->tryResponse(context: $context, fn: fn() => $this->action($context, $opts), action: $this->action);
}
/**

View File

@@ -12,6 +12,7 @@ use App\Backends\Common\Levels;
use App\Backends\Common\Response;
use App\Backends\Plex\PlexActionTrait;
use App\Backends\Plex\PlexClient;
use App\Libs\Exceptions\Backends\RuntimeException;
use App\Libs\Options;
use JsonException;
use JsonMachine\Exception\InvalidArgumentException;
@@ -20,7 +21,6 @@ use JsonMachine\JsonDecoder\DecodingError;
use JsonMachine\JsonDecoder\ErrorWrappingDecoder;
use JsonMachine\JsonDecoder\ExtJsonDecoder;
use Psr\Log\LoggerInterface as iLogger;
use RuntimeException;
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface as iHttp;
@@ -30,6 +30,8 @@ final class GetLibrary
use CommonTrait;
use PlexActionTrait;
private string $action = 'plex.getLibrary';
public function __construct(protected iHttp $http, protected iLogger $logger)
{
}
@@ -46,12 +48,17 @@ final class GetLibrary
*/
public function __invoke(Context $context, iGuid $guid, string|int $id, array $opts = []): Response
{
return $this->tryResponse(context: $context, fn: fn() => $this->action($context, $guid, $id, $opts));
return $this->tryResponse(
context: $context,
fn: fn() => $this->action($context, $guid, $id, $opts),
action: $this->action
);
}
/**
* @throws ExceptionInterface
* @throws InvalidArgumentException
* @throws RuntimeException
*/
private function action(Context $context, iGuid $guid, string|int $id, array $opts = []): Response
{
@@ -101,14 +108,14 @@ final class GetLibrary
);
}
$url = $context->backendUrl->withPath(
r('/library/sections/{library_id}/all', ['library_id' => $id])
)->withQuery(
http_build_query([
'type' => PlexClient::TYPE_MOVIE === ag($logContext, 'library.type') ? 1 : 2,
'includeGuids' => 1,
])
);
$url = $context->backendUrl
->withPath(r('/library/sections/{library_id}/all', ['library_id' => $id]))
->withQuery(
http_build_query([
'type' => PlexClient::TYPE_MOVIE === ag($logContext, 'library.type') ? 1 : 2,
'includeGuids' => 1,
])
);
$logContext['library']['url'] = (string)$url;
@@ -271,6 +278,18 @@ final class GetLibrary
return new Response(status: true, response: $list);
}
/**
* Process a single item.
*
* @param Context $context The context object.
* @param iGuid $guid The GUID object.
* @param array $item The item array.
* @param array $log The log array. Default is an empty array.
* @param array $opts The options array. Default is an empty array.
*
* @return array Returns an array containing the processed metadata.
* @throws RuntimeException Throws a RuntimeException if an unexpected item type is encountered while parsing the library.
*/
private function process(Context $context, iGuid $guid, array $item, array $log = [], array $opts = []): array
{
$url = $context->backendUrl->withPath(r('/library/metadata/{item_id}', ['item_id' => ag($item, 'ratingKey')]));

View File

@@ -18,6 +18,8 @@ final class GetMetaData
{
use CommonTrait;
private string $action = 'plex.getMetadata';
public function __construct(
protected HttpClientInterface $http,
protected LoggerInterface $logger,
@@ -111,6 +113,7 @@ final class GetMetaData
return new Response(status: true, response: $item, extra: ['cached' => $fromCache]);
},
action: $this->action
);
}
}

View File

@@ -18,6 +18,7 @@ use Throwable;
final class GetUserToken
{
private int $maxRetry = 3;
private string $action = 'plex.getUserToken';
use CommonTrait;
@@ -39,7 +40,8 @@ final class GetUserToken
{
return $this->tryResponse(
context: $context,
fn: fn() => $this->getUserToken($context, $userId, $username)
fn: fn() => $this->getUserToken($context, $userId, $username),
action: $this->action,
);
}

View File

@@ -20,6 +20,7 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
final class GetUsersList
{
private int $maxRetry = 3;
private string $action = 'plex.getUsersList';
use CommonTrait;
@@ -37,7 +38,11 @@ final class GetUsersList
*/
public function __invoke(Context $context, array $opts = []): Response
{
return $this->tryResponse(context: $context, fn: fn() => $this->getUsers($context, $opts));
return $this->tryResponse(
context: $context,
fn: fn() => $this->getUsers($context, $opts),
action: $this->action
);
}
/**

View File

@@ -15,7 +15,7 @@ final class GetVersion
{
use CommonTrait;
private string $action = 'get version';
private string $action = 'plex.getVersion';
public function __construct(
protected HttpClientInterface $http,

View File

@@ -11,13 +11,13 @@ use App\Backends\Common\Response;
use App\Backends\Plex\PlexActionTrait;
use App\Backends\Plex\PlexClient;
use App\Libs\Entity\StateInterface as iState;
use App\Libs\Exceptions\Backends\InvalidArgumentException;
use App\Libs\Guid;
use App\Libs\Mappers\ImportInterface as iImport;
use App\Libs\Message;
use App\Libs\Options;
use Closure;
use DateTimeInterface as iDate;
use InvalidArgumentException;
use JsonException;
use JsonMachine\Items;
use JsonMachine\JsonDecoder\DecodingError;
@@ -35,6 +35,8 @@ class Import
use CommonTrait;
use PlexActionTrait;
private string $action = 'plex.import';
public function __construct(protected iHttp $http, protected iLogger $logger)
{
}
@@ -55,42 +57,46 @@ class Import
iDate|null $after = null,
array $opts = []
): Response {
return $this->tryResponse($context, fn() => $this->getLibraries(
return $this->tryResponse(
context: $context,
handle: fn(array $logContext = []) => fn(iResponse $response) => $this->handle(
fn: fn() => $this->getLibraries(
context: $context,
response: $response,
callback: fn(array $item, array $logContext = []) => $this->process(
handle: fn(array $logContext = []) => fn(iResponse $response) => $this->handle(
context: $context,
guid: $guid,
mapper: $mapper,
item: $item,
logContext: $logContext,
opts: $opts + ['after' => $after],
response: $response,
callback: fn(array $item, array $logContext = []) => $this->process(
context: $context,
guid: $guid,
mapper: $mapper,
item: $item,
logContext: $logContext,
opts: $opts + ['after' => $after],
),
logContext: $logContext
),
error: fn(array $logContext = []) => fn(Throwable $e) => $this->logger->error(
message: 'Exception [{error.kind}] was thrown unhandled during [{client}: {backend}] library [{library.title}] request. Error [{error.message} @ {error.file}:{error.line}].',
context: [
'backend' => $context->backendName,
'client' => $context->clientName,
'error' => [
'kind' => $e::class,
'line' => $e->getLine(),
'message' => $e->getMessage(),
'file' => after($e->getFile(), ROOT_PATH),
],
...$logContext,
'exception' => [
'file' => $e->getFile(),
'line' => $e->getLine(),
'kind' => get_class($e),
'message' => $e->getMessage(),
],
]
),
logContext: $logContext
),
error: fn(array $logContext = []) => fn(Throwable $e) => $this->logger->error(
message: 'Exception [{error.kind}] was thrown unhandled during [{client}: {backend}] library [{library.title}] request. Error [{error.message} @ {error.file}:{error.line}].',
context: [
'backend' => $context->backendName,
'client' => $context->clientName,
'error' => [
'kind' => $e::class,
'line' => $e->getLine(),
'message' => $e->getMessage(),
'file' => after($e->getFile(), ROOT_PATH),
],
...$logContext,
'exception' => [
'file' => $e->getFile(),
'line' => $e->getLine(),
'kind' => get_class($e),
'message' => $e->getMessage(),
],
]
),
));
action: $this->action
);
}
protected function getLibraries(Context $context, Closure $handle, Closure $error): array

View File

@@ -13,6 +13,8 @@ final class InspectRequest
{
use CommonTrait;
private string $action = 'plex.inspectRequest';
public function __invoke(Context $context, ServerRequestInterface $request): Response
{
return $this->tryResponse(
@@ -59,7 +61,8 @@ final class InspectRequest
}
return new Response(status: true, response: $alteredRequest);
}
},
action: $this->action
);
}
}

View File

@@ -46,6 +46,8 @@ final class ParseWebhook
'media.pause',
];
private string $action = 'plex.parseWebhook';
/**
* Parse Webhook payload.
*
@@ -57,7 +59,11 @@ final class ParseWebhook
*/
public function __invoke(Context $context, iGuid $guid, iRequest $request): Response
{
return $this->tryResponse(context: $context, fn: fn() => $this->parse($context, $guid, $request));
return $this->tryResponse(
context: $context,
fn: fn() => $this->parse($context, $guid, $request),
action: $this->action
);
}
private function parse(Context $context, iGuid $guid, iRequest $request): Response

View File

@@ -10,6 +10,8 @@ use App\Backends\Common\GuidInterface as iGuid;
use App\Backends\Common\Response;
use App\Backends\Plex\PlexActionTrait;
use App\Libs\Entity\StateInterface as iState;
use App\Libs\Exceptions\Backends\InvalidArgumentException;
use App\Libs\Exceptions\Backends\RuntimeException;
use App\Libs\Options;
use App\Libs\QueueRequests;
use DateTimeInterface;
@@ -22,6 +24,8 @@ class Progress
use CommonTrait;
use PlexActionTrait;
private string $action = 'plex.progress';
public function __construct(protected HttpClientInterface $http, protected LoggerInterface $logger)
{
}
@@ -43,13 +47,11 @@ class Progress
QueueRequests $queue,
DateTimeInterface|null $after = null
): Response {
return $this->tryResponse(context: $context, fn: fn() => $this->action(
$context,
$guid,
$entities,
$queue,
$after
), action: 'plex.progress');
return $this->tryResponse(
context: $context,
fn: fn() => $this->action($context, $guid, $entities, $queue, $after),
action: $this->action
);
}
private function action(
@@ -160,7 +162,7 @@ class Progress
);
continue;
}
} catch (\RuntimeException $e) {
} catch (\App\Libs\Exceptions\RuntimeException|RuntimeException|InvalidArgumentException $e) {
$this->logger->error(
message: 'Exception [{error.kind}] was thrown unhandled during [{client}: {backend}] request to get {item.type} [{item.title}] status. Error [{error.message} @ {error.file}:{error.line}].',
context: [

View File

@@ -19,6 +19,8 @@ final class Push
{
use CommonTrait;
private string $action = 'plex.push';
public function __construct(protected HttpClientInterface $http, protected LoggerInterface $logger)
{
}
@@ -38,7 +40,11 @@ final class Push
QueueRequests $queue,
DateTimeInterface|null $after = null
): Response {
return $this->tryResponse(context: $context, fn: fn() => $this->action($context, $entities, $queue, $after));
return $this->tryResponse(
context: $context,
fn: fn() => $this->action($context, $entities, $queue, $after),
action: $this->action
);
}
private function action(

View File

@@ -17,6 +17,8 @@ final class SearchId
use CommonTrait;
use PlexActionTrait;
private string $action = 'plex.searchId';
public function __construct(protected HttpClientInterface $http, protected LoggerInterface $logger)
{
}
@@ -32,12 +34,22 @@ final class SearchId
*/
public function __invoke(Context $context, string|int $id, array $opts = []): Response
{
return $this->tryResponse(context: $context, fn: fn() => $this->search($context, $id, $opts));
return $this->tryResponse(
context: $context,
fn: fn() => $this->search($context, $id, $opts),
action: $this->action
);
}
private function search(Context $context, string|int $id, array $opts = []): Response
{
$item = $this->getItemDetails($context, $id, $opts);
$item = $this->getItemInfo($context, $id, $opts + [Options::NO_THROW => true]);
if (!$item->isSuccessful()) {
return $item;
}
$item = $item->response;
$metadata = ag($item, 'MediaContainer.Metadata.0', []);

View File

@@ -19,6 +19,8 @@ final class SearchQuery
{
use CommonTrait;
private string $action = 'plex.searchQuery';
public function __construct(protected HttpClientInterface $http, protected LoggerInterface $logger)
{
}
@@ -35,14 +37,18 @@ final class SearchQuery
*/
public function __invoke(Context $context, string $query, int $limit = 25, array $opts = []): Response
{
return $this->tryResponse(context: $context, fn: fn() => $this->search($context, $query, $limit, $opts));
return $this->tryResponse(
context: $context,
fn: fn() => $this->search($context, $query, $limit, $opts),
action: $this->action
);
}
/**
* Search Backend Titles.
*
* @throws ExceptionInterface
* @throws JsonException
* @throws ExceptionInterface if the request failed
* @throws JsonException if the response cannot be parsed
*/
private function search(Context $context, string $query, int $limit = 25, array $opts = []): Response
{

View File

@@ -10,7 +10,6 @@ use App\Libs\Config;
use App\Libs\Options;
use App\Libs\Routable;
use Psr\Log\LoggerInterface as iLogger;
use RuntimeException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -79,12 +78,17 @@ final class AccessTokenCommand extends Command
if (($config = $input->getOption('config'))) {
try {
Config::save('servers', Yaml::parseFile($this->checkCustomBackendsFile($config)));
} catch (RuntimeException $e) {
$this->logger->error($e->getMessage());
} catch (\App\Libs\Exceptions\RuntimeException $e) {
$output->writeln(r('<error>{message}</error>', ['message' => $e->getMessage()]));
return self::FAILURE;
}
}
if (null === ag(Config::get('servers', []), $backend, null)) {
$output->writeln(r("<error>ERROR: Backend '{backend}' not found.</error>", ['backend' => $backend]));
return self::FAILURE;
}
$opts = $backendOpts = [];
if ($input->getOption('include-raw-response')) {

View File

@@ -6,6 +6,7 @@ namespace App\Backends\Plex\Commands;
use App\Backends\Plex\PlexClient;
use App\Command;
use App\Libs\Exceptions\Backends\RuntimeException;
use App\Libs\Options;
use App\Libs\Routable;
use Psr\Log\LoggerInterface as iLogger;
@@ -66,6 +67,7 @@ final class DiscoverCommand extends Command
* @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface
* @throws ServerExceptionInterface
* @throws RuntimeException
*/
protected function runCommand(InputInterface $input, OutputInterface $output, null|array $rerun = null): int
{

View File

@@ -6,15 +6,16 @@ namespace App\Backends\Plex;
use App\Backends\Common\Context;
use App\Backends\Common\GuidInterface as iGuid;
use App\Backends\Common\Response;
use App\Backends\Plex\Action\GetLibrariesList;
use App\Backends\Plex\Action\GetMetaData;
use App\Libs\Container;
use App\Libs\Entity\StateEntity;
use App\Libs\Entity\StateInterface as iState;
use App\Libs\Exceptions\Backends\InvalidArgumentException;
use App\Libs\Exceptions\Backends\RuntimeException;
use App\Libs\Guid;
use App\Libs\Options;
use InvalidArgumentException;
use RuntimeException;
trait PlexActionTrait
{
@@ -33,6 +34,8 @@ trait PlexActionTrait
* @param array $opts options
*
* @return iState Return object on successful creation.
* @throws InvalidArgumentException if no date was set on object.
* @throws RuntimeException if request failed.
*/
protected function createEntity(Context $context, iGuid $guid, array $item, array $opts = []): iState
{
@@ -46,7 +49,7 @@ trait PlexActionTrait
}
if (null === $date) {
throw new RuntimeException('No date was set on object.');
throw new InvalidArgumentException('No date was set on object.');
}
$year = (int)ag($item, ['grandParentYear', 'parentYear', 'year'], 0);
@@ -195,11 +198,13 @@ trait PlexActionTrait
* @param Context $context
* @param string|int $id
* @param array $opts
*
* @return array
* @throws RuntimeException if request failed.
*/
protected function getItemDetails(Context $context, string|int $id, array $opts = []): array
{
$response = Container::get(GetMetaData::class)(context: $context, id: $id, opts: $opts);
$response = $this->getItemInfo(context: $context, id: $id, opts: $opts);
if ($response->isSuccessful()) {
return $response->response;
@@ -208,6 +213,19 @@ trait PlexActionTrait
throw new RuntimeException(message: $response->error->format(), previous: $response->error->previous);
}
/**
* Get item details.
*
* @param Context $context
* @param string|int $id
* @param array $opts
* @return Response
*/
protected function getItemInfo(Context $context, string|int $id, array $opts = []): Response
{
return Container::get(GetMetaData::class)(context: $context, id: $id, opts: $opts);
}
/**
* Get episode parent external ids.
*
@@ -217,6 +235,7 @@ trait PlexActionTrait
* @param array $logContext
*
* @return array
* @throws RuntimeException
*/
protected function getEpisodeParent(Context $context, iGuid $guid, int|string $id, array $logContext = []): array
{
@@ -278,6 +297,7 @@ trait PlexActionTrait
/**
* Get Backend Libraries details.
* @throws RuntimeException
*/
protected function getBackendLibraries(Context $context, array $opts = []): array
{

View File

@@ -29,6 +29,7 @@ use App\Backends\Plex\Action\SearchQuery;
use App\Libs\Config;
use App\Libs\Container;
use App\Libs\Entity\StateInterface as iState;
use App\Libs\Exceptions\Backends\RuntimeException;
use App\Libs\Exceptions\HttpException;
use App\Libs\Mappers\ImportInterface as iImport;
use App\Libs\Options;
@@ -38,7 +39,9 @@ use DateTimeInterface as iDate;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Log\LoggerInterface as iLogger;
use RuntimeException;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
@@ -377,7 +380,7 @@ class PlexClient implements iClient
{
$response = Container::get(SearchId::class)(context: $this->context, id: $id, opts: $opts);
if ($response->hasError()) {
if ($response->hasError() && false === (bool)ag($opts, Options::NO_LOGGING, false)) {
$this->logger->log($response->error->level(), $response->error->message, $response->error->context);
}
@@ -544,8 +547,11 @@ class PlexClient implements iClient
* @param array $opts (Optional) options.
*
* @return array The list of Plex servers.
* @throws RuntimeException When an unexpected status code is returned or a network-related exception occurs.
*
* @throws RuntimeException When an unexpected status code is returned or a network-related exception occurs.
* @throws ClientExceptionInterface When a client error is encountered.
* @throws RedirectionExceptionInterface When a redirection error is encountered.
* @throws ServerExceptionInterface When a server error is encountered.
*/
public static function discover(HttpClientInterface $http, string $token, array $opts = []): array
{

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace App\Backends\Plex;
use App\Backends\Common\ManageInterface;
use App\Libs\Exceptions\Backends\RuntimeException;
use App\Libs\Options;
use Psr\Log\LoggerInterface as iLogger;
use RuntimeException;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface as iInput;
use Symfony\Component\Console\Output\OutputInterface as iOutput;