Added backend:info & backend:version commands.

This commit is contained in:
abdulmohsen
2023-11-24 14:33:26 +03:00
parent 4c5237c8a3
commit 1f74775b28
15 changed files with 602 additions and 111 deletions

View File

@@ -211,4 +211,23 @@ interface ClientInterface
* @return string|bool return user token as string or bool(FALSE) if not supported.
*/
public function getUserToken(int|string $userId, string $username): string|bool;
/**
* Get Backend Info.
*
* @param array $opts
*
* @return array
*/
public function getInfo(array $opts = []): array;
/**
* Get Backend Version.
*
* @param array $opts
*
* @return string
*/
public function getVersion(array $opts = []): string;
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace App\Backends\Emby\Action;
class GetInfo extends \App\Backends\Jellyfin\Action\GetInfo
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace App\Backends\Emby\Action;
class GetVersion extends \App\Backends\Jellyfin\Action\GetVersion
{
}

View File

@@ -11,6 +11,7 @@ use App\Backends\Common\GuidInterface as iGuid;
use App\Backends\Emby\Action\Backup;
use App\Backends\Emby\Action\Export;
use App\Backends\Emby\Action\GetIdentifier;
use App\Backends\Emby\Action\GetInfo;
use App\Backends\Emby\Action\GetLibrariesList;
use App\Backends\Emby\Action\GetLibrary;
use App\Backends\Emby\Action\GetMetaData;
@@ -22,6 +23,7 @@ use App\Backends\Emby\Action\Progress;
use App\Backends\Emby\Action\Push;
use App\Backends\Emby\Action\SearchId;
use App\Backends\Emby\Action\SearchQuery;
use App\Backends\Jellyfin\Action\GetVersion;
use App\Backends\Jellyfin\JellyfinClient;
use App\Libs\Config;
use App\Libs\Container;
@@ -395,6 +397,36 @@ class EmbyClient implements iClient
return $response->response;
}
public function getInfo(array $opts = []): array
{
$response = Container::get(GetInfo::class)(context: $this->context, opts: $opts);
if ($response->hasError()) {
$this->logger->log($response->error->level(), $response->error->message, $response->error->context);
}
if (false === $response->isSuccessful()) {
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
}
return $response->response;
}
public function getVersion(array $opts = []): string
{
$response = Container::get(GetVersion::class)(context: $this->context, opts: $opts);
if ($response->hasError()) {
$this->logger->log($response->error->level(), $response->error->message, $response->error->context);
}
if (false === $response->isSuccessful()) {
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
}
return $response->response;
}
public static function manage(array $backend, array $opts = []): array
{
return Container::get(EmbyManage::class)->manage(backend: $backend, opts: $opts);

View File

@@ -6,8 +6,6 @@ namespace App\Backends\Jellyfin\Action;
use App\Backends\Common\CommonTrait;
use App\Backends\Common\Context;
use App\Backends\Common\Error;
use App\Backends\Common\Levels;
use App\Backends\Common\Response;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;
@@ -39,59 +37,13 @@ class GetIdentifier
return $this->tryResponse(
context: $context,
fn: function () use ($context, $opts) {
$url = $context->backendUrl->withPath('/system/Info');
$info = (new GetInfo($this->http, $this->logger, $this->cache))(context: $context, opts: $opts);
$this->logger->debug('Requesting [{client}: {backend}] unique identifier.', [
'client' => $context->clientName,
'backend' => $context->backendName,
'url' => $url
]);
$response = $this->http->request(
'GET',
(string)$url,
array_replace_recursive($context->backendHeaders, $opts['headers'] ?? [])
);
$content = $response->getContent(false);
if (200 !== $response->getStatusCode()) {
return new Response(
status: false,
error: new Error(
message: 'Request for [{backend}] {action} returned with unexpected [{status_code}] status code.',
context: [
'action' => $this->action,
'client' => $context->clientName,
'backend' => $context->backendName,
'status_code' => $response->getStatusCode(),
'url' => (string)$url,
'response' => $content,
],
level: Levels::WARNING
)
);
if (false === $info->status) {
return $info;
}
$item = json_decode(
json: $content,
associative: true,
flags: JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_IGNORE
);
if (true === $context->trace) {
$this->logger->debug('Processing [{client}: {backend}] {action} payload.', [
'action' => $this->action,
'client' => $context->clientName,
'backend' => $context->backendName,
'trace' => $item,
]);
}
return new Response(
status: true,
response: ag($item, 'Id', null)
);
return new Response(status: true, response: ag($info->response, 'identifier'));
},
action: $this->action,
);

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace App\Backends\Jellyfin\Action;
use App\Backends\Common\CommonTrait;
use App\Backends\Common\Context;
use App\Backends\Common\Error;
use App\Backends\Common\Levels;
use App\Backends\Common\Response;
use App\Libs\Options;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class GetInfo
{
use CommonTrait;
private string $action = 'get info';
public function __construct(
protected HttpClientInterface $http,
protected LoggerInterface $logger,
protected CacheInterface $cache
) {
}
/**
* Get Backend unique identifier.
*
* @param Context $context
* @param array $opts optional options.
*
* @return Response
*/
public function __invoke(Context $context, array $opts = []): Response
{
return $this->tryResponse(
context: $context,
fn: function () use ($context, $opts) {
$url = $context->backendUrl->withPath('/system/Info');
$this->logger->debug('Requesting [{client}: {backend}] info.', [
'client' => $context->clientName,
'backend' => $context->backendName,
'url' => $url
]);
$response = $this->http->request(
'GET',
(string)$url,
array_replace_recursive($context->backendHeaders, $opts['headers'] ?? [])
);
$content = $response->getContent(false);
if (200 !== $response->getStatusCode()) {
return new Response(
status: false,
error: new Error(
message: 'Request for [{backend}] {action} returned with unexpected [{status_code}] status code.',
context: [
'action' => $this->action,
'client' => $context->clientName,
'backend' => $context->backendName,
'status_code' => $response->getStatusCode(),
'url' => (string)$url,
'response' => $content,
],
level: Levels::WARNING
)
);
}
$item = json_decode(
json: $content,
associative: true,
flags: JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_IGNORE
);
if (true === $context->trace) {
$this->logger->debug('Processing [{client}: {backend}] {action} payload.', [
'action' => $this->action,
'client' => $context->clientName,
'backend' => $context->backendName,
'trace' => $item,
]);
}
$ret = [
'type' => $context->clientName,
'name' => ag($item, 'ServerName'),
'version' => ag($item, 'Version'),
'identifier' => ag($item, 'Id'),
'platform' => ag($item, 'OperatingSystem'),
];
if (true === ag_exists($opts, Options::RAW_RESPONSE)) {
$ret[Options::RAW_RESPONSE] = $item;
}
return new Response(status: true, response: $ret);
},
action: $this->action,
);
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace App\Backends\Jellyfin\Action;
use App\Backends\Common\CommonTrait;
use App\Backends\Common\Context;
use App\Backends\Common\Response;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class GetVersion
{
use CommonTrait;
private string $action = 'get version';
public function __construct(
protected HttpClientInterface $http,
protected LoggerInterface $logger,
protected CacheInterface $cache
) {
}
/**
* Get Backend unique identifier.
*
* @param Context $context
* @param array $opts optional options.
*
* @return Response
*/
public function __invoke(Context $context, array $opts = []): Response
{
return $this->tryResponse(
context: $context,
fn: function () use ($context, $opts) {
$info = (new GetInfo($this->http, $this->logger, $this->cache))(context: $context, opts: $opts);
if (false === $info->status) {
return $info;
}
return new Response(status: true, response: ag($info->response, 'version'));
},
action: $this->action,
);
}
}

View File

@@ -11,10 +11,12 @@ use App\Backends\Common\GuidInterface as iGuid;
use App\Backends\Jellyfin\Action\Backup;
use App\Backends\Jellyfin\Action\Export;
use App\Backends\Jellyfin\Action\GetIdentifier;
use App\Backends\Jellyfin\Action\GetInfo;
use App\Backends\Jellyfin\Action\GetLibrariesList;
use App\Backends\Jellyfin\Action\GetLibrary;
use App\Backends\Jellyfin\Action\GetMetaData;
use App\Backends\Jellyfin\Action\GetUsersList;
use App\Backends\Jellyfin\Action\GetVersion;
use App\Backends\Jellyfin\Action\Import;
use App\Backends\Jellyfin\Action\InspectRequest;
use App\Backends\Jellyfin\Action\ParseWebhook;
@@ -410,6 +412,36 @@ class JellyfinClient implements iClient
return $response->response;
}
public function getInfo(array $opts = []): array
{
$response = Container::get(GetInfo::class)(context: $this->context, opts: $opts);
if ($response->hasError()) {
$this->logger->log($response->error->level(), $response->error->message, $response->error->context);
}
if (false === $response->isSuccessful()) {
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
}
return $response->response;
}
public function getVersion(array $opts = []): string
{
$response = Container::get(GetVersion::class)(context: $this->context, opts: $opts);
if ($response->hasError()) {
$this->logger->log($response->error->level(), $response->error->message, $response->error->context);
}
if (false === $response->isSuccessful()) {
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
}
return $response->response;
}
public static function manage(array $backend, array $opts = []): array
{
return Container::get(JellyfinManage::class)->manage(backend: $backend, opts: $opts);

View File

@@ -6,8 +6,6 @@ namespace App\Backends\Plex\Action;
use App\Backends\Common\CommonTrait;
use App\Backends\Common\Context;
use App\Backends\Common\Error;
use App\Backends\Common\Levels;
use App\Backends\Common\Response;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;
@@ -39,59 +37,13 @@ final class GetIdentifier
return $this->tryResponse(
context: $context,
fn: function () use ($context, $opts) {
$url = $context->backendUrl->withPath('/');
$info = (new GetInfo($this->http, $this->logger, $this->cache))(context: $context, opts: $opts);
$this->logger->debug('Requesting [{client}: {backend}] unique identifier.', [
'client' => $context->clientName,
'backend' => $context->backendName,
'url' => $url
]);
$response = $this->http->request(
'GET',
(string)$url,
array_replace_recursive($context->backendHeaders, $opts['headers'] ?? [])
);
$content = $response->getContent(false);
if (200 !== $response->getStatusCode()) {
return new Response(
status: false,
error: new Error(
message: 'Request for [{backend}] {action} returned with unexpected [{status_code}] status code.',
context: [
'action' => $this->action,
'client' => $context->clientName,
'backend' => $context->backendName,
'status_code' => $response->getStatusCode(),
'url' => (string)$url,
'response' => $content,
],
level: Levels::WARNING
)
);
if (false === $info->status) {
return $info;
}
$item = json_decode(
json: $content,
associative: true,
flags: JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_IGNORE
);
if (true === $context->trace) {
$this->logger->debug('Processing [{client}: {backend}] {action} payload.', [
'action' => $this->action,
'client' => $context->clientName,
'backend' => $context->backendName,
'trace' => $item,
]);
}
return new Response(
status: true,
response: ag($item, 'MediaContainer.machineIdentifier', null)
);
return new Response(status: true, response: ag($info->response, 'identifier'));
},
action: $this->action
);

View File

@@ -0,0 +1,114 @@
<?php
declare(strict_types=1);
namespace App\Backends\Plex\Action;
use App\Backends\Common\CommonTrait;
use App\Backends\Common\Context;
use App\Backends\Common\Error;
use App\Backends\Common\Levels;
use App\Backends\Common\Response;
use App\Libs\Options;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
final class GetInfo
{
use CommonTrait;
private string $action = 'get info';
public function __construct(
protected HttpClientInterface $http,
protected LoggerInterface $logger,
protected CacheInterface $cache
) {
}
/**
* Get Backend unique identifier.
*
* @param Context $context
* @param array $opts optional options.
*
* @return Response
*/
public function __invoke(Context $context, array $opts = []): Response
{
return $this->tryResponse(
context: $context,
fn: function () use ($context, $opts) {
$url = $context->backendUrl->withPath('/');
$this->logger->debug('Requesting [{client}: {backend}] unique identifier.', [
'client' => $context->clientName,
'backend' => $context->backendName,
'url' => $url
]);
$response = $this->http->request(
'GET',
(string)$url,
array_replace_recursive($context->backendHeaders, $opts['headers'] ?? [])
);
$content = $response->getContent(false);
if (200 !== $response->getStatusCode()) {
return new Response(
status: false,
error: new Error(
message: 'Request for [{backend}] {action} returned with unexpected [{status_code}] status code.',
context: [
'action' => $this->action,
'client' => $context->clientName,
'backend' => $context->backendName,
'status_code' => $response->getStatusCode(),
'url' => (string)$url,
'response' => $content,
],
level: Levels::WARNING
)
);
}
$item = json_decode(
json: $content,
associative: true,
flags: JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_IGNORE
);
if (true === $context->trace) {
$this->logger->debug('Processing [{client}: {backend}] {action} payload.', [
'action' => $this->action,
'client' => $context->clientName,
'backend' => $context->backendName,
'trace' => $item,
]);
}
$data = ag($item, 'MediaContainer', []);
$ret = [
'type' => $context->clientName,
'name' => ag($data, 'friendlyName', null),
'version' => ag($data, 'version', null),
'identifier' => ag($data, 'machineIdentifier', null),
'platform' => ag($data, 'platform', null),
];
if (true === ag_exists($opts, Options::RAW_RESPONSE)) {
$ret[Options::RAW_RESPONSE] = $data;
}
return new Response(
status: true,
response: $ret,
);
},
action: $this->action
);
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace App\Backends\Plex\Action;
use App\Backends\Common\CommonTrait;
use App\Backends\Common\Context;
use App\Backends\Common\Response;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
final class GetVersion
{
use CommonTrait;
private string $action = 'get version';
public function __construct(
protected HttpClientInterface $http,
protected LoggerInterface $logger,
protected CacheInterface $cache
) {
}
/**
* Get Backend unique identifier.
*
* @param Context $context
* @param array $opts optional options.
*
* @return Response
*/
public function __invoke(Context $context, array $opts = []): Response
{
return $this->tryResponse(
context: $context,
fn: function () use ($context, $opts) {
$info = (new GetInfo($this->http, $this->logger, $this->cache))(context: $context, opts: $opts);
if (false === $info->status) {
return $info;
}
return new Response(status: true, response: ag($info->response, 'version'));
},
action: $this->action
);
}
}

View File

@@ -11,11 +11,13 @@ use App\Backends\Common\GuidInterface as iGuid;
use App\Backends\Plex\Action\Backup;
use App\Backends\Plex\Action\Export;
use App\Backends\Plex\Action\GetIdentifier;
use App\Backends\Plex\Action\GetInfo;
use App\Backends\Plex\Action\GetLibrariesList;
use App\Backends\Plex\Action\GetLibrary;
use App\Backends\Plex\Action\GetMetaData;
use App\Backends\Plex\Action\GetUsersList;
use App\Backends\Plex\Action\GetUserToken;
use App\Backends\Plex\Action\GetVersion;
use App\Backends\Plex\Action\Import;
use App\Backends\Plex\Action\InspectRequest;
use App\Backends\Plex\Action\ParseWebhook;
@@ -406,6 +408,36 @@ class PlexClient implements iClient
return $response->response;
}
public function getInfo(array $opts = []): array
{
$response = Container::get(GetInfo::class)(context: $this->context, opts: $opts);
if ($response->hasError()) {
$this->logger->log($response->error->level(), $response->error->message, $response->error->context);
}
if (false === $response->isSuccessful()) {
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
}
return $response->response;
}
public function getVersion(array $opts = []): string
{
$response = Container::get(GetVersion::class)(context: $this->context, opts: $opts);
if ($response->hasError()) {
$this->logger->log($response->error->level(), $response->error->message, $response->error->context);
}
if (false === $response->isSuccessful()) {
throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format()));
}
return $response->response;
}
public static function manage(array $backend, array $opts = []): array
{
return Container::get(PlexManage::class)->manage(backend: $backend, opts: $opts);

View File

@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace App\Commands\Backend;
use App\Command;
use App\Libs\Config;
use App\Libs\Options;
use App\Libs\Routable;
use RuntimeException;
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\Yaml\Yaml;
#[Routable(command: self::ROUTE)]
class InfoCommand extends Command
{
public const ROUTE = 'backend:info';
protected function configure(): void
{
$this->setName(self::ROUTE)
->setDescription('Get backend product info.')
->addOption('include-raw-response', null, InputOption::VALUE_NONE, 'Include unfiltered raw response.')
->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Use Alternative config file.')
->addArgument('backend', InputArgument::REQUIRED, 'Backend name to restore.');
}
protected function runCommand(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('backend');
$mode = $input->getOption('output');
$opts = [];
if (($config = $input->getOption('config'))) {
try {
Config::save('servers', Yaml::parseFile($this->checkCustomBackendsFile($config)));
} catch (RuntimeException $e) {
$output->writeln(sprintf('<error>%s</error>', $e->getMessage()));
return self::FAILURE;
}
}
if (null === ag(Config::get('servers', []), $name, null)) {
$output->writeln(sprintf('<error>ERROR: Backend \'%s\' not found.</error>', $name));
return self::FAILURE;
}
if ($input->getOption('include-raw-response')) {
$opts[Options::RAW_RESPONSE] = true;
}
$backend = $this->getBackend($name);
$info = $backend->getInfo($opts);
if ('table' === $mode) {
foreach ($info as $_ => &$val) {
if (false === is_bool($val)) {
continue;
}
$val = $val ? 'Yes' : 'No';
}
$info = [$info];
}
$this->displayContent($info, $output, $mode);
return self::SUCCESS;
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace App\Commands\Backend;
use App\Command;
use App\Libs\Config;
use App\Libs\Routable;
use RuntimeException;
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\Yaml\Yaml;
#[Routable(command: self::ROUTE)]
class VersionCommand extends Command
{
public const ROUTE = 'backend:version';
protected function configure(): void
{
$this->setName(self::ROUTE)
->setDescription('Get backend product version.')
->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Use Alternative config file.')
->addArgument('backend', InputArgument::REQUIRED, 'Backend name to restore.');
}
protected function runCommand(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('backend');
if (($config = $input->getOption('config'))) {
try {
Config::save('servers', Yaml::parseFile($this->checkCustomBackendsFile($config)));
} catch (RuntimeException $e) {
$output->writeln(sprintf('<error>%s</error>', $e->getMessage()));
return self::FAILURE;
}
}
if (null === ag(Config::get('servers', []), $name, null)) {
$output->writeln(sprintf('<error>ERROR: Backend \'%s\' not found.</error>', $name));
return self::FAILURE;
}
$output->writeln($this->getBackend($name)->getVersion());
return self::SUCCESS;
}
}