Merge pull request #169 from ArabCoders/dev

Cleaned up shared import/export code.
This commit is contained in:
Abdulmohsen
2022-06-20 19:10:27 +03:00
committed by GitHub
8 changed files with 100 additions and 216 deletions

View File

@@ -5,97 +5,46 @@ declare(strict_types=1);
namespace App\Backends\Jellyfin\Action;
use App\Backends\Common\Context;
use App\Backends\Common\GuidInterface as iGuid;
use App\Backends\Common\Response;
use App\Backends\Jellyfin\JellyfinClient as JFC;
use App\Backends\Common\GuidInterface;
use App\Backends\Jellyfin\JellyfinClient;
use App\Libs\Container;
use App\Libs\Data;
use App\Libs\Entity\StateInterface as iFace;
use App\Libs\Mappers\ImportInterface;
use App\Libs\Options;
use App\Libs\QueueRequests;
use DateTimeInterface;
use Symfony\Contracts\HttpClient\ResponseInterface as iResponse;
use Throwable;
class Export extends Import
{
/**
* @param Context $context
* @param iGuid $guid
* @param ImportInterface $mapper
* @param DateTimeInterface|null $after
* @param array $opts
*
* @return Response
*/
public function __invoke(
protected function process(
Context $context,
iGuid $guid,
ImportInterface $mapper,
DateTimeInterface|null $after = null,
array $opts = []
): Response {
return $this->tryResponse($context, fn() => $this->getLibraries(
context: $context,
handle: fn(array $logContext = []) => fn(iResponse $response) => $this->handle(
context: $context,
response: $response,
callback: fn(array $item, array $logContext = []) => $this->export(
context: $context,
guid: $guid,
queue: $opts['queue'],
mapper: $mapper,
item: $item,
logContext: $logContext,
opts: ['after' => $after],
),
logContext: $logContext
),
error: fn(array $logContext = []) => fn(Throwable $e) => $this->logger->error(
'Unhandled Exception was thrown during [%(backend)] library [%(library.title)] request.',
[
'backend' => $context->backendName,
...$logContext,
'exception' => [
'file' => $e->getFile(),
'line' => $e->getLine(),
'kind' => get_class($e),
'message' => $e->getMessage(),
],
]
),
));
}
private function export(
Context $context,
iGuid $guid,
QueueRequests $queue,
GuidInterface $guid,
ImportInterface $mapper,
array $item,
array $logContext = [],
array $opts = [],
): void {
if (JFC::TYPE_SHOW === ($type = ag($item, 'Type'))) {
if (JellyfinClient::TYPE_SHOW === ($type = ag($item, 'Type'))) {
$this->processShow(context: $context, guid: $guid, item: $item, logContext: $logContext);
return;
}
try {
$after = ag($opts, 'after');
$type = JFC::TYPE_MAPPER[$type];
$queue = ag($opts, 'queue', fn() => Container::get(QueueRequests::class));
$after = ag($opts, 'after', null);
Data::increment($context->backendName, $type . '_total');
$logContext['item'] = [
'id' => ag($item, 'Id'),
'title' => match ($type) {
iFace::TYPE_MOVIE => sprintf(
JellyfinClient::TYPE_MOVIE => sprintf(
'%s (%d)',
ag($item, ['Name', 'OriginalTitle'], '??'),
ag($item, 'ProductionYear', 0000)
ag($item, 'ProductionYear', '0000')
),
iFace::TYPE_EPISODE => trim(
JellyfinClient::TYPE_EPISODE => trim(
sprintf(
'%s - (%sx%s)',
ag($item, 'SeriesName', '??'),
@@ -241,22 +190,24 @@ class Export extends Import
]
);
if (false === (bool)ag($context->options, Options::DRY_RUN, false)) {
$queue->add(
$this->http->request(
$entity->isWatched() ? 'POST' : 'DELETE',
(string)$url,
$context->backendHeaders + [
'user_data' => [
'context' => $logContext + [
'backend' => $context->backendName,
'play_state' => $entity->isWatched() ? 'Played' : 'Unplayed',
],
],
]
)
);
if (true === (bool)ag($context->options, Options::DRY_RUN, false)) {
return;
}
$queue->add(
$this->http->request(
$entity->isWatched() ? 'POST' : 'DELETE',
(string)$url,
$context->backendHeaders + [
'user_data' => [
'context' => $logContext + [
'backend' => $context->backendName,
'play_state' => $entity->isWatched() ? 'Played' : 'Unplayed',
],
],
]
)
);
} catch (Throwable $e) {
$this->logger->error(
'Unhandled exception was thrown during handling of [%(backend)] [%(library.title)] [%(item.title)] export.',

View File

@@ -59,13 +59,13 @@ class Import
handle: fn(array $logContext = []) => fn(iResponse $response) => $this->handle(
context: $context,
response: $response,
callback: fn(array $item, array $logContext = []) => $this->import(
callback: fn(array $item, array $logContext = []) => $this->process(
context: $context,
guid: $guid,
mapper: $mapper,
item: $item,
logContext: $logContext,
opts: ['after' => $after],
opts: $opts + ['after' => $after],
),
logContext: $logContext
),
@@ -105,7 +105,7 @@ class Import
'status_code' => $response->getStatusCode(),
]
);
Data::add($context->backendName, 'no_import_update', true);
Data::add($context->backendName, 'has_errors', true);
return [];
}
@@ -120,11 +120,9 @@ class Import
if (empty($listDirs)) {
$this->logger->warning('Request for [%(backend)] libraries returned with empty list.', [
'backend' => $context->backendName,
'context' => [
'body' => $json,
]
'body' => $json,
]);
Data::add($context->backendName, 'no_import_update', true);
Data::add($context->backendName, 'has_errors', true);
return [];
}
} catch (ExceptionInterface $e) {
@@ -137,7 +135,7 @@ class Import
'message' => $e->getMessage(),
],
]);
Data::add($context->backendName, 'no_import_update', true);
Data::add($context->backendName, 'has_errors', true);
return [];
} catch (JsonException $e) {
$this->logger->error('Request for [%(backend)] libraries returned with invalid body.', [
@@ -147,7 +145,7 @@ class Import
'message' => $e->getMessage(),
],
]);
Data::add($context->backendName, 'no_import_update', true);
Data::add($context->backendName, 'has_errors', true);
return [];
}
@@ -311,7 +309,7 @@ class Import
'unsupported' => $unsupported,
],
]);
Data::add($context->backendName, 'no_import_update', true);
Data::add($context->backendName, 'has_errors', true);
return [];
}
@@ -421,7 +419,7 @@ class Import
$this->logger->debug('Processing [%(backend)] %(item.type) [%(item.title) (%(item.year))] payload.', [
'backend' => $context->backendName,
...$logContext,
'trace' => $item,
'body' => $item,
]);
}
@@ -437,9 +435,7 @@ class Import
$this->logger->info($message, [
'backend' => $context->backendName,
...$logContext,
'data' => [
'guids' => !empty($providersId) ? $providersId : 'None'
],
'guids' => !empty($providersId) ? $providersId : 'None'
]);
return;
@@ -454,7 +450,7 @@ class Import
);
}
private function import(
protected function process(
Context $context,
iGuid $guid,
ImportInterface $mapper,
@@ -495,9 +491,7 @@ class Import
$this->logger->debug('Processing [%(backend)] %(item.type) [%(item.title)] payload.', [
'backend' => $context->backendName,
...$logContext,
'response' => [
'body' => $item
],
'body' => $item
]);
}
@@ -509,9 +503,7 @@ class Import
'backend' => $context->backendName,
'date_key' => $dateKey,
...$logContext,
'response' => [
'body' => $item,
],
'body' => $item,
]);
Data::increment($context->backendName, $type . '_ignored_no_date_is_set');
@@ -565,9 +557,7 @@ class Import
$this->logger->info($message, [
'backend' => $context->backendName,
...$logContext,
'context' => [
'guids' => !empty($providerIds) ? $providerIds : 'None'
],
'guids' => !empty($providerIds) ? $providerIds : 'None'
]);
Data::increment($context->backendName, $type . '_ignored_no_supported_guid');

View File

@@ -5,76 +5,27 @@ declare(strict_types=1);
namespace App\Backends\Plex\Action;
use App\Backends\Common\Context;
use App\Backends\Common\GuidInterface as iGuid;
use App\Backends\Common\Response;
use App\Backends\Common\GuidInterface;
use App\Backends\Plex\PlexClient;
use App\Libs\Container;
use App\Libs\Data;
use App\Libs\Mappers\ImportInterface;
use App\Libs\Options;
use App\Libs\QueueRequests;
use DateTimeInterface;
use Symfony\Contracts\HttpClient\ResponseInterface as iResponse;
use Throwable;
final class Export extends Import
{
/**
* @param Context $context
* @param iGuid $guid
* @param ImportInterface $mapper
* @param DateTimeInterface|null $after
* @param array $opts
*
* @return Response
*/
public function __invoke(
protected function process(
Context $context,
iGuid $guid,
ImportInterface $mapper,
DateTimeInterface|null $after = null,
array $opts = []
): Response {
return $this->tryResponse($context, fn() => $this->getLibraries(
context: $context,
handle: fn(array $logContext = []) => fn(iResponse $response) => $this->handle(
context: $context,
response: $response,
callback: fn(array $item, array $logContext = []) => $this->export(
context: $context,
guid: $guid,
queue: $opts['queue'],
mapper: $mapper,
item: $item,
logContext: $logContext,
opts: ['after' => $after],
),
logContext: $logContext,
),
error: fn(array $logContext = []) => fn(Throwable $e) => $this->logger->error(
'Unhandled Exception was thrown during [%(backend)] library [%(library.title)] request.',
[
'backend' => $context->backendName,
...$logContext,
'exception' => [
'file' => $e->getFile(),
'line' => $e->getLine(),
'kind' => get_class($e),
'message' => $e->getMessage(),
],
]
),
));
}
private function export(
Context $context,
iGuid $guid,
QueueRequests $queue,
GuidInterface $guid,
ImportInterface $mapper,
array $item,
array $logContext = [],
array $opts = [],
): void {
$queue = ag($opts, 'queue', fn() => Container::get(QueueRequests::class));
$after = ag($opts, 'after', null);
$library = ag($logContext, 'library.id');
$type = ag($item, 'type');
@@ -253,22 +204,24 @@ final class Export extends Import
]
);
if (false === (bool)ag($context->options, Options::DRY_RUN, false)) {
$queue->add(
$this->http->request(
'GET',
(string)$url,
array_replace_recursive($context->backendHeaders, [
'user_data' => [
'context' => $logContext + [
'backend' => $context->backendName,
'play_state' => $entity->isWatched() ? 'Played' : 'Unplayed',
],
]
])
)
);
if (true === (bool)ag($context->options, Options::DRY_RUN, false)) {
return;
}
$queue->add(
$this->http->request(
'GET',
(string)$url,
array_replace_recursive($context->backendHeaders, [
'user_data' => [
'context' => $logContext + [
'backend' => $context->backendName,
'play_state' => $entity->isWatched() ? 'Played' : 'Unplayed',
],
]
])
)
);
} catch (Throwable $e) {
$this->logger->error(
'Unhandled exception was thrown during handling of [%(backend)] [%(library.title)] [%(item.title)] export.',

View File

@@ -59,13 +59,13 @@ class Import
handle: fn(array $logContext = []) => fn(iResponse $response) => $this->handle(
context: $context,
response: $response,
callback: fn(array $item, array $logContext = []) => $this->import(
callback: fn(array $item, array $logContext = []) => $this->process(
context: $context,
guid: $guid,
mapper: $mapper,
item: $item,
logContext: $logContext,
opts: ['after' => $after],
opts: $opts + ['after' => $after],
),
logContext: $logContext
),
@@ -106,7 +106,7 @@ class Import
]
);
Data::add($context->backendName, 'no_import_update', true);
Data::add($context->backendName, 'has_errors', true);
return [];
}
@@ -121,11 +121,9 @@ class Import
if (empty($listDirs)) {
$this->logger->warning('Request for [%(backend)] libraries returned with empty list.', [
'backend' => $context->backendName,
'context' => [
'body' => $json,
]
'body' => $json,
]);
Data::add($context->backendName, 'no_import_update', true);
Data::add($context->backendName, 'has_errors', true);
return [];
}
} catch (ExceptionInterface $e) {
@@ -138,7 +136,7 @@ class Import
'message' => $e->getMessage(),
],
]);
Data::add($context->backendName, 'no_import_update', true);
Data::add($context->backendName, 'has_errors', true);
return [];
} catch (JsonException $e) {
$this->logger->error('Request for [%(backend)] libraries returned with invalid body.', [
@@ -148,7 +146,7 @@ class Import
'message' => $e->getMessage(),
],
]);
Data::add($context->backendName, 'no_import_update', true);
Data::add($context->backendName, 'has_errors', true);
return [];
}
@@ -333,7 +331,7 @@ class Import
],
]);
Data::add($context->backendName, 'no_import_update', true);
Data::add($context->backendName, 'has_errors', true);
return [];
}
@@ -428,10 +426,14 @@ class Import
protected function processShow(Context $context, iGuid $guid, array $item, array $logContext = []): void
{
if (null === ($item['Guid'] ?? null)) {
$item['Guid'] = [['id' => $item['guid']]];
} else {
$item['Guid'][] = ['id' => $item['guid']];
$guids = [];
if (null !== ($item['Guid'] ?? null)) {
$guids = $item['Guid'];
}
if (null !== ($itemGuid = ag($item, 'guid')) && false === $guid->isLocal($itemGuid)) {
$guids[] = ['id' => $itemGuid];
}
$year = (int)ag($item, ['grandParentYear', 'parentYear', 'year'], 0);
@@ -454,22 +456,14 @@ class Import
$this->logger->debug('Processing [%(backend)] %(item.type) [%(item.title) (%(item.year))].', [
'backend' => $context->backendName,
...$logContext,
'trace' => $item,
'body' => $item,
]);
}
if (!$guid->has($item['Guid'])) {
if (null === ($item['Guid'] ?? null)) {
$item['Guid'] = [];
}
if (null !== ($item['guid'] ?? null) && false === $guid->isLocal($item['guid'])) {
$item['Guid'][] = ['id' => $item['guid']];
}
if (!$guid->has($guids)) {
$message = 'Ignoring [%(backend)] [%(item.title)]. %(item.type) has no valid/supported external ids.';
if (empty($item['Guid'] ?? [])) {
if (empty($guids)) {
$message .= ' Most likely unmatched %(item.type).';
}
@@ -487,19 +481,19 @@ class Import
$gContext = ag_set(
$logContext,
'item.plex_id',
str_starts_with(ag($item, 'guid', ''), 'plex://') ? ag($item, 'guid') : 'none'
str_starts_with($itemGuid ?? 'None', 'plex://') ? ag($item, 'guid') : 'none'
);
$context->cache->set(
PlexClient::TYPE_SHOW . '.' . ag($logContext, 'item.id'),
Guid::fromArray(
payload: $guid->get($item['Guid'], context: [...$gContext]),
context: ['backend' => $context->backendName, ...$logContext,]
payload: $guid->get($guids, context: [...$gContext]),
context: ['backend' => $context->backendName, ...$logContext]
)->getAll()
);
}
private function import(
protected function process(
Context $context,
iGuid $guid,
ImportInterface $mapper,
@@ -547,7 +541,7 @@ class Import
$this->logger->debug('Processing [%(backend)] %(item.type) [%(item.title)]', [
'backend' => $context->backendName,
...$logContext,
'payload' => $item,
'body' => $item,
]);
}
@@ -556,9 +550,7 @@ class Import
'backend' => $context->backendName,
'date_key' => true === (bool)ag($item, 'viewCount', false) ? 'lastViewedAt' : 'addedAt',
...$logContext,
'response' => [
'body' => $item,
],
'body' => $item,
]);
Data::increment($context->backendName, $type . '_ignored_no_date_is_set');
@@ -615,9 +607,7 @@ class Import
$this->logger->info($message, [
'backend' => $context->backendName,
...$logContext,
'context' => [
'guids' => !empty($item['Guid']) ? $item['Guid'] : 'None'
],
'guids' => !empty($item['Guid']) ? $item['Guid'] : 'None'
]);
Data::increment($context->backendName, $type . '_ignored_no_supported_guid');

View File

@@ -79,7 +79,7 @@ final class RunCommand extends Command
if (0 === $count) {
$this->write(
sprintf('[%s] <info>No Tasks Scheduled to run at this time.</info>', makeDate()),
sprintf('[%s] No Tasks Scheduled to run at this time.', makeDate()),
$input,
$output,
OutputInterface::VERBOSITY_VERY_VERBOSE
@@ -101,7 +101,7 @@ final class RunCommand extends Command
$this->write('Command: ' . $task->getCommand() . ' ' . $task->getArgs(), $input, $output);
$this->write('Date: ' . makeDate(), $input, $output);
$this->write('--------------------------', $input, $output);
$this->write(sprintf('Task %s Output.', $task->getName()), $input, $output);
$this->write(sprintf('Task [%s] Output.', $task->getName()), $input, $output);
$this->write('--------------------------', $input, $output);
$this->write('', $input, $output);
}

View File

@@ -329,7 +329,7 @@ class ExportCommand extends Command
continue;
}
if (true === (bool)Data::get(sprintf('%s.no_export_update', $name))) {
if (true === (bool)Data::get(sprintf('%s.has_errors', $name))) {
$this->logger->notice(
sprintf('%s: Not updating last export date. Backend reported an error.', $name)
);
@@ -444,7 +444,7 @@ class ExportCommand extends Command
array_push($requests, ...$backend['class']->export($this->mapper, $this->queue, $after));
if (false === $input->getOption('dry-run')) {
if (true === (bool)Data::get(sprintf('%s.no_export_update', $name))) {
if (true === (bool)Data::get(sprintf('%s.has_errors', $name))) {
$this->logger->notice('Not updating last export date. [%(backend)] report an error.', [
'backend' => $name,
]);

View File

@@ -248,7 +248,7 @@ class ImportCommand extends Command
$inDryMode = $this->mapper->inDryRunMode() || ag($server, 'options.' . Options::DRY_RUN);
if (false === Data::get(sprintf('%s.no_import_update', $name)) && false === $inDryMode) {
if (false === Data::get(sprintf('%s.has_errors', $name)) && false === $inDryMode) {
Config::save(sprintf('servers.%s.import.lastSync', $name), time());
}
}

View File

@@ -316,15 +316,15 @@ final class Task
$stdout = $this->process->getOutput();
$stderr = $this->process->getErrorOutput();
if (!empty($stdout)) {
$this->output .= $stdout;
if (!empty($stderr)) {
$this->output .= $stderr;
}
if (!empty($stderr)) {
if (!empty($this->output)) {
if (!empty($stdout)) {
if (!empty($stderr)) {
$this->output .= PHP_EOL;
}
$this->output .= $stderr;
$this->output .= $stdout;
}
$this->process = null;