More documentations update.
This commit is contained in:
@@ -83,7 +83,9 @@ class Progress
|
||||
];
|
||||
|
||||
if ($context->backendName === $entity->via) {
|
||||
$this->logger->info('Ignoring [{item.title}] for [{client}: {backend}]. Event generator.', [
|
||||
$this->logger->info(
|
||||
'Emby.Progress: Ignoring [{item.title}] for [{client}: {backend}]. Event generator.',
|
||||
[
|
||||
'client' => $context->clientName,
|
||||
'backend' => $context->backendName,
|
||||
...$logContext,
|
||||
@@ -93,21 +95,27 @@ class Progress
|
||||
}
|
||||
|
||||
if (null === ag($metadata, iState::COLUMN_ID, null)) {
|
||||
$this->logger->warning('Ignoring [{item.title}] for [{client}: {backend}]. No metadata.', [
|
||||
'client' => $context->clientName,
|
||||
'backend' => $context->backendName,
|
||||
...$logContext,
|
||||
]);
|
||||
$this->logger->warning(
|
||||
'Emby.Progress: Ignoring [{item.title}] for [{client}: {backend}]. No metadata.',
|
||||
[
|
||||
'client' => $context->clientName,
|
||||
'backend' => $context->backendName,
|
||||
...$logContext,
|
||||
]
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$senderDate = ag($entity->getExtra($entity->via), iState::COLUMN_EXTRA_DATE);
|
||||
if (null === $senderDate) {
|
||||
$this->logger->warning('Ignoring [{item.title}] for [{client}: {backend}]. Sender date is not set.', [
|
||||
'client' => $context->clientName,
|
||||
'backend' => $context->backendName,
|
||||
...$logContext,
|
||||
]);
|
||||
$this->logger->warning(
|
||||
'Emby.Progress: Ignoring [{item.title}] for [{client}: {backend}]. Sender date is not set.',
|
||||
[
|
||||
'client' => $context->clientName,
|
||||
'backend' => $context->backendName,
|
||||
...$logContext,
|
||||
]
|
||||
);
|
||||
continue;
|
||||
}
|
||||
$senderDate = makeDate($senderDate)->getTimestamp();
|
||||
@@ -115,7 +123,7 @@ class Progress
|
||||
$datetime = ag($entity->getExtra($context->backendName), iState::COLUMN_EXTRA_DATE, null);
|
||||
if (false === $ignoreDate && null !== $datetime && makeDate($datetime)->getTimestamp() > $senderDate) {
|
||||
$this->logger->warning(
|
||||
'Ignoring [{item.title}] for [{client}: {backend}]. Sender date is older than recorded backend date.',
|
||||
'Emby.Progress: Ignoring [{item.title}] for [{client}: {backend}]. Sender date is older than recorded backend date.',
|
||||
[
|
||||
'client' => $context->clientName,
|
||||
'backend' => $context->backendName,
|
||||
@@ -138,7 +146,7 @@ class Progress
|
||||
|
||||
if (false === $ignoreDate && makeDate($remoteItem->updated)->getTimestamp() > $senderDate) {
|
||||
$this->logger->info(
|
||||
'Ignoring [{item.title}] for [{client}: {backend}]. Sender date is older than backend remote item date.',
|
||||
'Emby.Progress: Ignoring [{item.title}] for [{client}: {backend}]. Sender date is older than backend remote item date.',
|
||||
[
|
||||
'client' => $context->clientName,
|
||||
'backend' => $context->backendName,
|
||||
@@ -150,7 +158,7 @@ class Progress
|
||||
|
||||
if ($remoteItem->isWatched()) {
|
||||
$this->logger->info(
|
||||
'Ignoring [{item.title}] for [{client}: {backend}]. The backend reported the item as watched.',
|
||||
'Emby.Progress: Ignoring [{item.title}] for [{client}: {backend}]. The backend reported the item as watched.',
|
||||
[
|
||||
'client' => $context->clientName,
|
||||
'backend' => $context->backendName,
|
||||
@@ -194,13 +202,16 @@ class Progress
|
||||
|
||||
$logContext['remote']['url'] = (string)$url;
|
||||
|
||||
$this->logger->debug('Updating [{client}: {backend}] {item.type} [{item.title}] watch progress.', [
|
||||
// -- convert time to ticks for emby to understand it.
|
||||
'time' => floor($entity->getPlayProgress() * 1_00_00),
|
||||
'client' => $context->clientName,
|
||||
'backend' => $context->backendName,
|
||||
...$logContext,
|
||||
]);
|
||||
$this->logger->debug(
|
||||
'Emby.Progress: Updating [{client}: {backend}] {item.type} [{item.title}] watch progress.',
|
||||
[
|
||||
// -- convert time to ticks for emby to understand it.
|
||||
'time' => floor($entity->getPlayProgress() * 1_00_00),
|
||||
'client' => $context->clientName,
|
||||
'backend' => $context->backendName,
|
||||
...$logContext,
|
||||
]
|
||||
);
|
||||
|
||||
if (false === (bool)ag($context->options, Options::DRY_RUN, false)) {
|
||||
$queue->add(
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Backends\Common\Cache;
|
||||
use App\Backends\Common\ClientInterface as iClient;
|
||||
use App\Backends\Common\Context;
|
||||
use App\Backends\Common\GuidInterface as iGuid;
|
||||
use App\Backends\Common\Response;
|
||||
use App\Backends\Emby\Action\Backup;
|
||||
use App\Backends\Emby\Action\Export;
|
||||
use App\Backends\Emby\Action\GetIdentifier;
|
||||
@@ -42,8 +43,9 @@ use SplFileObject;
|
||||
/**
|
||||
* Class EmbyClient
|
||||
*
|
||||
* This class represents a client for the Emby backend.
|
||||
* It implements the iClient interface.
|
||||
* This class is responsible for facilitating communication with Emby Server backend.
|
||||
*
|
||||
* @implements iClient
|
||||
*/
|
||||
class EmbyClient implements iClient
|
||||
{
|
||||
@@ -200,10 +202,7 @@ class EmbyClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new HttpException(
|
||||
ag($response->extra, 'message', fn() => $response->error->format()),
|
||||
ag($response->extra, 'http_code', 400),
|
||||
);
|
||||
$this->throwError($response, HttpException::class, ag($response->extra, 'http_code', 400));
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
@@ -229,7 +228,7 @@ class EmbyClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
@@ -252,7 +251,7 @@ class EmbyClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
@@ -279,7 +278,7 @@ class EmbyClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
@@ -302,7 +301,7 @@ class EmbyClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return [];
|
||||
@@ -326,7 +325,7 @@ class EmbyClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return [];
|
||||
@@ -349,7 +348,7 @@ class EmbyClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
@@ -367,7 +366,7 @@ class EmbyClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
@@ -385,7 +384,7 @@ class EmbyClient implements iClient
|
||||
);
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(message: $response->error->format(), previous: $response->error->previous);
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
@@ -403,7 +402,7 @@ class EmbyClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
@@ -439,9 +438,7 @@ class EmbyClient implements iClient
|
||||
$this->logger->log($response->error->level(), $response->error->message, $response->error->context);
|
||||
}
|
||||
|
||||
throw new RuntimeException(
|
||||
ag($response->extra, 'message', fn() => $response->error->format())
|
||||
);
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
@@ -475,7 +472,7 @@ class EmbyClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
@@ -493,7 +490,7 @@ class EmbyClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
@@ -511,7 +508,7 @@ class EmbyClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
@@ -524,4 +521,21 @@ class EmbyClient implements iClient
|
||||
{
|
||||
return Container::get(EmbyManage::class)->manage(backend: $backend, opts: $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception with the specified message and previous exception.
|
||||
*
|
||||
* @template T
|
||||
* @param Response $response The response object containing the error details.
|
||||
* @param class-string<T> $className The exception class name.
|
||||
* @param int $code The exception code.
|
||||
*/
|
||||
private function throwError(Response $response, string $className = RuntimeException::class, int $code = 0): void
|
||||
{
|
||||
throw new $className(
|
||||
message: ag($response->extra, 'message', fn() => $response->error->format()),
|
||||
code: $code,
|
||||
previous: $response->error->previous
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Backends\Common\Cache;
|
||||
use App\Backends\Common\ClientInterface as iClient;
|
||||
use App\Backends\Common\Context;
|
||||
use App\Backends\Common\GuidInterface as iGuid;
|
||||
use App\Backends\Common\Response;
|
||||
use App\Backends\Plex\Action\Backup;
|
||||
use App\Backends\Plex\Action\Export;
|
||||
use App\Backends\Plex\Action\GetIdentifier;
|
||||
@@ -38,12 +39,16 @@ use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface as iLogger;
|
||||
use RuntimeException;
|
||||
use SplFileObject;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Class PlexClient
|
||||
*
|
||||
* This class is responsible for facilitating communication with Plex Server backend.
|
||||
*
|
||||
* @implements iClient
|
||||
*/
|
||||
class PlexClient implements iClient
|
||||
{
|
||||
public const NAME = 'PlexBackend';
|
||||
@@ -54,12 +59,18 @@ class PlexClient implements iClient
|
||||
public const TYPE_MOVIE = 'movie';
|
||||
public const TYPE_EPISODE = 'episode';
|
||||
|
||||
/**
|
||||
* @var array Map plex types to iState types.
|
||||
*/
|
||||
public const TYPE_MAPPER = [
|
||||
PlexClient::TYPE_SHOW => iState::TYPE_SHOW,
|
||||
PlexClient::TYPE_MOVIE => iState::TYPE_MOVIE,
|
||||
PlexClient::TYPE_EPISODE => iState::TYPE_EPISODE,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array List of supported agents.
|
||||
*/
|
||||
public const SUPPORTED_AGENTS = [
|
||||
'com.plexapp.agents.imdb',
|
||||
'com.plexapp.agents.tmdb',
|
||||
@@ -73,11 +84,31 @@ class PlexClient implements iClient
|
||||
'tv.plex.agents.movie',
|
||||
'tv.plex.agents.series',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var mixed $context Backend context.
|
||||
*/
|
||||
private Context $context;
|
||||
/**
|
||||
* @var iLogger The logger object.
|
||||
*/
|
||||
private iLogger $logger;
|
||||
/**
|
||||
* @var iGuid GUID parser.
|
||||
*/
|
||||
private iGuid $guid;
|
||||
/**
|
||||
* @var Cache The Cache store.
|
||||
*/
|
||||
private Cache $cache;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param iLogger $logger The logger instance.
|
||||
* @param Cache $cache The cache instance.
|
||||
* @param PlexGuid $guid The PlexGuid instance.
|
||||
*/
|
||||
public function __construct(iLogger $logger, Cache $cache, PlexGuid $guid)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
@@ -91,6 +122,9 @@ class PlexClient implements iClient
|
||||
$this->guid = $guid->withContext($this->context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function withContext(Context $context): self
|
||||
{
|
||||
$cloned = clone $this;
|
||||
@@ -125,16 +159,25 @@ class PlexClient implements iClient
|
||||
return $cloned;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getContext(): Context
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->context?->backendName ?? static::CLIENT_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setLogger(iLogger $logger): self
|
||||
{
|
||||
$this->logger = $logger;
|
||||
@@ -142,6 +185,9 @@ class PlexClient implements iClient
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function processRequest(ServerRequestInterface $request, array $opts = []): ServerRequestInterface
|
||||
{
|
||||
$response = Container::get(InspectRequest::class)(context: $this->context, request: $request);
|
||||
@@ -153,6 +199,9 @@ class PlexClient implements iClient
|
||||
return $response->isSuccessful() ? $response->response : $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function parseWebhook(ServerRequestInterface $request): iState
|
||||
{
|
||||
$response = Container::get(ParseWebhook::class)(
|
||||
@@ -166,15 +215,15 @@ class PlexClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new HttpException(
|
||||
ag($response->extra, 'message', fn() => $response->error->format()),
|
||||
ag($response->extra, 'http_code', 400),
|
||||
);
|
||||
$this->throwError($response, HttpException::class, ag($response->extra, 'http_code', 400));
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function pull(iImport $mapper, iDate|null $after = null): array
|
||||
{
|
||||
$response = Container::get(Import::class)(
|
||||
@@ -192,12 +241,15 @@ class PlexClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function backup(iImport $mapper, SplFileObject|null $writer = null, array $opts = []): array
|
||||
{
|
||||
$response = Container::get(Backup::class)(
|
||||
@@ -212,12 +264,15 @@ class PlexClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function export(iImport $mapper, QueueRequests $queue, iDate|null $after = null): array
|
||||
{
|
||||
$response = Container::get(Export::class)(
|
||||
@@ -236,12 +291,15 @@ class PlexClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function push(array $entities, QueueRequests $queue, iDate|null $after = null): array
|
||||
{
|
||||
$response = Container::get(Push::class)(
|
||||
@@ -256,12 +314,15 @@ class PlexClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function progress(array $entities, QueueRequests $queue, iDate|null $after = null): array
|
||||
{
|
||||
$response = Container::get(Progress::class)(
|
||||
@@ -277,12 +338,15 @@ class PlexClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function search(string $query, int $limit = 25, array $opts = []): array
|
||||
{
|
||||
$response = Container::get(SearchQuery::class)(
|
||||
@@ -297,12 +361,15 @@ class PlexClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function searchId(string|int $id, array $opts = []): array
|
||||
{
|
||||
$response = Container::get(SearchId::class)(context: $this->context, id: $id, opts: $opts);
|
||||
@@ -312,23 +379,29 @@ class PlexClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getMetadata(string|int $id, array $opts = []): array
|
||||
{
|
||||
$response = Container::get(GetMetaData::class)(context: $this->context, id: $id, opts: $opts);
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(message: $response->error->format(), previous: $response->error->previous);
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getLibrary(string|int $id, array $opts = []): array
|
||||
{
|
||||
$response = Container::get(GetLibrary::class)(context: $this->context, guid: $this->guid, id: $id, opts: $opts);
|
||||
@@ -338,12 +411,15 @@ class PlexClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getIdentifier(bool $forceRefresh = false): int|string|null
|
||||
{
|
||||
if (false === $forceRefresh && null !== $this->context->backendId) {
|
||||
@@ -359,6 +435,9 @@ class PlexClient implements iClient
|
||||
return $response->isSuccessful() ? $response->response : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getUsersList(array $opts = []): array
|
||||
{
|
||||
$response = Container::get(GetUsersList::class)($this->context, $opts);
|
||||
@@ -368,14 +447,15 @@ class PlexClient implements iClient
|
||||
$this->logger->log($response->error->level(), $response->error->message, $response->error->context);
|
||||
}
|
||||
|
||||
throw new RuntimeException(
|
||||
ag($response->extra, 'message', fn() => $response->error->format())
|
||||
);
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getUserToken(int|string $userId, string $username): string|bool
|
||||
{
|
||||
$response = Container::get(GetUserToken::class)($this->context, $userId, $username);
|
||||
@@ -385,14 +465,15 @@ class PlexClient implements iClient
|
||||
$this->logger->log($response->error->level(), $response->error->message, $response->error->context);
|
||||
}
|
||||
|
||||
throw new RuntimeException(
|
||||
ag($response->extra, 'message', fn() => $response->error->format())
|
||||
);
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function listLibraries(array $opts = []): array
|
||||
{
|
||||
$response = Container::get(GetLibrariesList::class)(context: $this->context, opts: $opts);
|
||||
@@ -402,12 +483,15 @@ class PlexClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getInfo(array $opts = []): array
|
||||
{
|
||||
$response = Container::get(GetInfo::class)(context: $this->context, opts: $opts);
|
||||
@@ -417,12 +501,15 @@ class PlexClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getVersion(array $opts = []): string
|
||||
{
|
||||
$response = Container::get(GetVersion::class)(context: $this->context, opts: $opts);
|
||||
@@ -432,23 +519,30 @@ class PlexClient implements iClient
|
||||
}
|
||||
|
||||
if (false === $response->isSuccessful()) {
|
||||
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
|
||||
$this->throwError($response);
|
||||
}
|
||||
|
||||
return $response->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public static function manage(array $backend, array $opts = []): array
|
||||
{
|
||||
return Container::get(PlexManage::class)->manage(backend: $backend, opts: $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover Servers linked to plex token.
|
||||
* Retrieves a list of Plex servers using the Plex.tv API.
|
||||
*
|
||||
* @param HttpClientInterface $http The HTTP client used to send the request.
|
||||
* @param string $token The Plex authentication token.
|
||||
* @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 ServerExceptionInterface
|
||||
* @throws RedirectionExceptionInterface
|
||||
* @throws ClientExceptionInterface
|
||||
*/
|
||||
public static function discover(HttpClientInterface $http, string $token, array $opts = []): array
|
||||
{
|
||||
@@ -463,19 +557,24 @@ class PlexClient implements iClient
|
||||
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
throw new RuntimeException(
|
||||
r('Request for servers list returned with unexpected [{status_code}] status code. {context}', [
|
||||
'status_code' => $response->getStatusCode(),
|
||||
'context' => arrayToString(['payload' => $payload]),
|
||||
])
|
||||
r(
|
||||
text: 'PlexClient: Request for servers list returned with unexpected [{status_code}] status code. {context}',
|
||||
context: [
|
||||
'status_code' => $response->getStatusCode(),
|
||||
'context' => arrayToString(['payload' => $payload]),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
throw new RuntimeException(
|
||||
r(
|
||||
'Unexpected exception [{exception}] was thrown during request for servers list, likely network related error. [{error}]',
|
||||
[
|
||||
'exception' => $e::class,
|
||||
text: 'PlexClient: Exception [{kind}] was thrown unhandled during request for plex servers list, likely network related error. [{error} @ {file}:{line}]',
|
||||
context: [
|
||||
'kind' => $e::class,
|
||||
'error' => $e->getMessage(),
|
||||
'line' => $e->getLine(),
|
||||
'file' => after($e->getFile(), ROOT_PATH),
|
||||
]
|
||||
)
|
||||
);
|
||||
@@ -486,7 +585,7 @@ class PlexClient implements iClient
|
||||
$list = [];
|
||||
|
||||
if (false === $xml->Device) {
|
||||
throw new RuntimeException('No devices found associated with the given token.');
|
||||
throw new RuntimeException('PlexClient: No backends were associated with the given token.');
|
||||
}
|
||||
|
||||
foreach ($xml->Device as $device) {
|
||||
@@ -535,4 +634,21 @@ class PlexClient implements iClient
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception with the specified message and previous exception.
|
||||
*
|
||||
* @template T
|
||||
* @param Response $response The response object containing the error details.
|
||||
* @param class-string<T> $className The exception class name.
|
||||
* @param int $code The exception code.
|
||||
*/
|
||||
private function throwError(Response $response, string $className = RuntimeException::class, int $code = 0): void
|
||||
{
|
||||
throw new $className(
|
||||
message: ag($response->extra, 'message', fn() => $response->error->format()),
|
||||
code: $code,
|
||||
previous: $response->error->previous
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ use Throwable;
|
||||
|
||||
final class PlexGuid implements iGuid
|
||||
{
|
||||
/**
|
||||
* @var array<string,string> Map plex guids to our guids.
|
||||
*/
|
||||
private const GUID_MAPPER = [
|
||||
'imdb' => Guid::GUID_IMDB,
|
||||
'tmdb' => Guid::GUID_TMDB,
|
||||
@@ -22,6 +25,10 @@ final class PlexGuid implements iGuid
|
||||
'youtube' => Guid::GUID_YOUTUBE,
|
||||
'cmdb' => Guid::GUID_CMDB,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<array-key,string> List of legacy plex agents.
|
||||
*/
|
||||
private const GUID_LEGACY = [
|
||||
'com.plexapp.agents.imdb',
|
||||
'com.plexapp.agents.tmdb',
|
||||
@@ -33,11 +40,19 @@ final class PlexGuid implements iGuid
|
||||
'com.plexapp.agents.youtube',
|
||||
'com.plexapp.agents.cmdb',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<array-key,string> List of local plex agents.
|
||||
*/
|
||||
private const GUID_LOCAL = [
|
||||
'plex',
|
||||
'local',
|
||||
'com.plexapp.agents.none',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string,string> Map guids to their replacement.
|
||||
*/
|
||||
private const GUID_LEGACY_REPLACER = [
|
||||
'com.plexapp.agents.themoviedb://' => 'com.plexapp.agents.tmdb://',
|
||||
'com.plexapp.agents.xbmcnfotv://' => 'com.plexapp.agents.tvdb://',
|
||||
@@ -47,17 +62,24 @@ final class PlexGuid implements iGuid
|
||||
// -- otherwise fallback to tmdb.
|
||||
'com.plexapp.agents.xbmcnfo://' => 'com.plexapp.agents.tmdb://',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var Context|null Backend context.
|
||||
*/
|
||||
private Context|null $context = null;
|
||||
|
||||
/**
|
||||
* Class to handle Plex external ids Parsing.
|
||||
* Class constructor.
|
||||
*
|
||||
* @param LoggerInterface $logger
|
||||
* @param LoggerInterface $logger Logger instance.
|
||||
*/
|
||||
public function __construct(protected LoggerInterface $logger)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function withContext(Context $context): self
|
||||
{
|
||||
$cloned = clone $this;
|
||||
@@ -66,16 +88,25 @@ final class PlexGuid implements iGuid
|
||||
return $cloned;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function parse(array $guids, array $context = []): array
|
||||
{
|
||||
return $this->ListExternalIds(guids: $guids, context: $context, log: false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function get(array $guids, array $context = []): array
|
||||
{
|
||||
return $this->ListExternalIds(guids: $guids, context: $context, log: true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function has(array $guids, array $context = []): bool
|
||||
{
|
||||
return count($this->ListExternalIds(guids: $guids, context: $context, log: false)) >= 1;
|
||||
@@ -94,12 +125,13 @@ final class PlexGuid implements iGuid
|
||||
}
|
||||
|
||||
/**
|
||||
* List Supported External Ids.
|
||||
* List supported external ids.
|
||||
*
|
||||
* @param array $guids
|
||||
* @param array $context
|
||||
* @param array $guids List of guids.
|
||||
* @param array $context Context data.
|
||||
* @param bool $log Log errors. default true.
|
||||
* @return array
|
||||
*
|
||||
* @return array List of external ids.
|
||||
*/
|
||||
private function ListExternalIds(array $guids, array $context = [], bool $log = true): array
|
||||
{
|
||||
@@ -128,7 +160,7 @@ final class PlexGuid implements iGuid
|
||||
if (false === str_contains($val, '://')) {
|
||||
if (true === $log) {
|
||||
$this->logger->info(
|
||||
'Unable to parse [{backend}] [{agent}] identifier.',
|
||||
'PlexGuid: Unable to parse [{backend}] [{agent}] identifier.',
|
||||
[
|
||||
'backend' => $this->context->backendName,
|
||||
'agent' => $val,
|
||||
@@ -149,7 +181,7 @@ final class PlexGuid implements iGuid
|
||||
if (true === isIgnoredId($this->context->backendName, $type, $key, $value, $id)) {
|
||||
if (true === $log) {
|
||||
$this->logger->debug(
|
||||
'Ignoring [{backend}] external id [{source}] for {item.type} [{item.title}] as requested.',
|
||||
'PlexGuid: Ignoring [{backend}] external id [{source}] for {item.type} [{item.title}] as requested.',
|
||||
[
|
||||
'backend' => $this->context->backendName,
|
||||
'source' => $val,
|
||||
@@ -168,7 +200,7 @@ final class PlexGuid implements iGuid
|
||||
if (null !== ($guid[self::GUID_MAPPER[$key]] ?? null)) {
|
||||
if (true === $log) {
|
||||
$this->logger->debug(
|
||||
'[{backend}] reported multiple ids for same data source [{key}: {ids}] for {item.type} [{item.title}].',
|
||||
'PlexGuid: [{backend}] reported multiple ids for same data source [{key}: {ids}] for {item.type} [{item.title}].',
|
||||
[
|
||||
'backend' => $this->context->backendName,
|
||||
'key' => $key,
|
||||
@@ -191,7 +223,7 @@ final class PlexGuid implements iGuid
|
||||
} catch (Throwable $e) {
|
||||
if (true === $log) {
|
||||
$this->logger->error(
|
||||
message: 'Exception [{error.kind}] was thrown unhandled during [{client}: {backend}] parsing [{agent}] identifier. Error [{error.message} @ {error.file}:{error.line}].',
|
||||
message: 'PlexGuid: Exception [{error.kind}] was thrown unhandled during [{client}: {backend}] parsing [{agent}] identifier. Error [{error.message} @ {error.file}:{error.line}].',
|
||||
context: [
|
||||
'backend' => $this->context->backendName,
|
||||
'client' => $this->context->clientName,
|
||||
@@ -225,11 +257,11 @@ final class PlexGuid implements iGuid
|
||||
/**
|
||||
* Parse legacy plex agents.
|
||||
*
|
||||
* @param string $guid
|
||||
* @param array $context
|
||||
* @param string $guid Guid to parse.
|
||||
* @param array $context Context data.
|
||||
* @param bool $log Log errors. default true.
|
||||
*
|
||||
* @return string
|
||||
* @return string Parsed guid.
|
||||
* @see https://github.com/ZeroQI/Hama.bundle/issues/510
|
||||
*/
|
||||
private function parseLegacyAgent(string $guid, array $context = [], bool $log = true): string
|
||||
@@ -299,7 +331,7 @@ final class PlexGuid implements iGuid
|
||||
} catch (Throwable $e) {
|
||||
if (true === $log) {
|
||||
$this->logger->error(
|
||||
message: 'Exception [{error.kind}] was thrown unhandled during [{client}: {backend}] parsing legacy agent [{agent}] identifier. Error [{error.message} @ {error.file}:{error.line}].',
|
||||
message: 'PlexGuid: Exception [{error.kind}] was thrown unhandled during [{client}: {backend}] parsing legacy agent [{agent}] identifier. Error [{error.message} @ {error.file}:{error.line}].',
|
||||
context: [
|
||||
'backend' => $this->context->backendName,
|
||||
'client' => $this->context->clientName,
|
||||
|
||||
@@ -16,10 +16,28 @@ use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface as iHttp;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Class PlexManage
|
||||
*
|
||||
* This class is responsible for creating and managing Plex backends configuration.
|
||||
*
|
||||
* @implements ManageInterface.
|
||||
*/
|
||||
class PlexManage implements ManageInterface
|
||||
{
|
||||
/**
|
||||
* @var QuestionHelper The QuestionHelper object used for asking questions.
|
||||
*/
|
||||
private QuestionHelper $questionHelper;
|
||||
|
||||
/**
|
||||
* Constructor for the class.
|
||||
*
|
||||
* @param iHttp $http The iHttp object used for HTTP requests.
|
||||
* @param iOutput $output The iOutput object used for outputting data.
|
||||
* @param iInput $input The iInput object used for retrieving user input.
|
||||
* @param iLogger $logger The iLogger object used for logging.
|
||||
*/
|
||||
public function __construct(
|
||||
private iHttp $http,
|
||||
private iOutput $output,
|
||||
@@ -29,6 +47,9 @@ class PlexManage implements ManageInterface
|
||||
$this->questionHelper = new QuestionHelper();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function manage(array $backend, array $opts = []): array
|
||||
{
|
||||
// -- $backend.token
|
||||
|
||||
16
src/Libs/BackendException.php
Normal file
16
src/Libs/BackendException.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Libs;
|
||||
|
||||
use ErrorException;
|
||||
|
||||
/**
|
||||
* Class BackendException
|
||||
*
|
||||
* This class represents an exception that is thrown by the backend logic.
|
||||
*/
|
||||
class BackendException extends ErrorException
|
||||
{
|
||||
}
|
||||
@@ -1034,3 +1034,53 @@ if (false === function_exists('isValidURL')) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (false === function_exists('getSystemMemoryInfo')) {
|
||||
/**
|
||||
* Get system memory information.
|
||||
*
|
||||
* @return array{ MemTotal: float, MemFree: float, MemAvailable: float, SwapTotal: float, SwapFree: float }
|
||||
*/
|
||||
function getSystemMemoryInfo(): array
|
||||
{
|
||||
$keys = [
|
||||
'MemTotal' => 'mem_total',
|
||||
'MemFree' => 'mem_free',
|
||||
'MemAvailable' => 'mem_available',
|
||||
'SwapTotal' => 'swap_total',
|
||||
'SwapFree' => 'swap_free',
|
||||
];
|
||||
|
||||
$result = [];
|
||||
|
||||
if (!is_readable('/proc/meminfo')) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (false === ($lines = @file('/proc/meminfo', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES))) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (empty($line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$line = explode(':', $line);
|
||||
$key = trim($line[0]);
|
||||
|
||||
if (false === array_key_exists($key, $keys)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$val = trim(str_ireplace(' kB', '', $line[1]));
|
||||
|
||||
$value = 1000 * (float)$val;
|
||||
|
||||
$result[$keys[$key]] = $value;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user