Added Configurability via options class.

This commit is contained in:
Abdulmhsen B. A. A
2022-05-11 06:18:48 +03:00
parent c1113f79ae
commit b8c36f2b3b
10 changed files with 122 additions and 110 deletions

View File

@@ -14,8 +14,8 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Yaml\Yaml;
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
use Throwable;
class CacheCommand extends Command
{
@@ -173,12 +173,8 @@ class CacheCommand extends Command
foreach ($queue as $_key => $response) {
$requestData = $response->getInfo('user_data');
try {
if (200 === $response->getStatusCode()) {
$requestData['ok']($response);
} else {
$requestData['error']($response);
}
} catch (ExceptionInterface $e) {
$requestData['ok']($response);
} catch (Throwable $e) {
$requestData['error']($e);
}

View File

@@ -9,7 +9,7 @@ use App\Libs\Config;
use App\Libs\Data;
use App\Libs\Extends\CliLogger;
use App\Libs\Mappers\ExportInterface;
use App\Libs\Servers\ServerInterface;
use App\Libs\Options;
use App\Libs\Storage\PDO\PDOAdapter;
use App\Libs\Storage\StorageInterface;
use Psr\Log\LoggerInterface;
@@ -20,6 +20,7 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\HttpClient\Exception\ServerException;
use Symfony\Component\Yaml\Yaml;
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
use Throwable;
class ExportCommand extends Command
{
@@ -174,7 +175,7 @@ class ExportCommand extends Command
$opts = ag($server, 'options', []);
if ($input->getOption('ignore-date')) {
$opts[ServerInterface::OPT_EXPORT_IGNORE_DATE] = true;
$opts[Options::IGNORE_DATE] = true;
}
if ($input->getOption('proxy')) {
@@ -227,12 +228,8 @@ class ExportCommand extends Command
foreach ($requests as $response) {
$requestData = $response->getInfo('user_data');
try {
if (200 === $response->getStatusCode()) {
$requestData['ok']($response);
} else {
$requestData['error']($response);
}
} catch (ExceptionInterface $e) {
$requestData['ok']($response);
} catch (Throwable $e) {
$requestData['error']($e);
}
}

View File

@@ -10,6 +10,7 @@ use App\Libs\Data;
use App\Libs\Entity\StateInterface;
use App\Libs\Extends\CliLogger;
use App\Libs\Mappers\ImportInterface;
use App\Libs\Options;
use App\Libs\Storage\PDO\PDOAdapter;
use App\Libs\Storage\StorageInterface;
use Psr\Log\LoggerInterface;
@@ -20,8 +21,8 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Yaml\Yaml;
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
use Throwable;
class ImportCommand extends Command
{
@@ -70,20 +71,12 @@ class ImportCommand extends Command
'Sync selected servers, comma seperated. \'s1,s2\'.',
''
)
->addOption('stats-show', null, InputOption::VALUE_NONE, 'Show final status.')
->addOption(
'stats-filter',
null,
InputOption::VALUE_OPTIONAL,
'Filter final status output e.g. (servername.key)',
null
)
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Do not commit any changes.')
->addOption(
'deep-debug',
null,
InputOption::VALUE_NONE,
'You should not use this flag unless told by the team.'
'You should not use this flag unless told by the team it will inflate your log output.'
)
->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Use Alternative config file.');
}
@@ -129,11 +122,11 @@ class ImportCommand extends Command
if ($input->getOption('dry-run')) {
$output->writeln('<info>Dry run mode. No changes will be committed to backend.</info>');
$mapperOpts[ImportInterface::DRY_RUN] = true;
$mapperOpts[Options::DRY_RUN] = true;
}
if ($input->getOption('deep-debug')) {
$mapperOpts[ImportInterface::DEEP_DEBUG] = true;
$mapperOpts[Options::DEEP_DEBUG] = true;
}
if (!empty($mapperOpts)) {
@@ -207,6 +200,10 @@ class ImportCommand extends Command
$opts['client']['proxy'] = $input->getOption('proxy');
}
if ($input->getOption('deep-debug')) {
$opts[Options::DEEP_DEBUG] = true;
}
if ($input->getOption('no-proxy')) {
$opts['client']['no_proxy'] = $input->getOption('no-proxy');
}
@@ -222,25 +219,27 @@ class ImportCommand extends Command
$server['class'] = $server['class']->setLogger($logger);
}
$after = true === $input->getOption('force-full') ? null : ag($server, 'import.lastSync', null);
$after = ag($server, 'import.lastSync', null);
if (true === (bool)ag($opts, Options::FORCE_FULL, false) || true === $input->getOption('force-full')) {
$after = null;
}
if (null === $after) {
$this->logger->notice(
sprintf('Importing \'%s\' play state changes since beginning.', $name)
sprintf('%s: Importing play state changes since beginning.', $name)
);
} else {
$after = makeDate($after);
$this->logger->notice(
sprintf('Importing \'%s\' play state changes since \'%s\'.', $name, $after)
sprintf('%s: Importing play state changes since \'%s\'.', $name, $after)
);
}
array_push($queue, ...$server['class']->pull($this->mapper, $after));
if (true === Data::get(sprintf('%s.no_import_update', $name))) {
$this->logger->notice(
sprintf('Not updating \'%s\' last sync time as the server reported an error.', $name)
);
$this->logger->notice(sprintf('%s: Not updating last sync date backend reported an error.', $name));
} else {
Config::save(sprintf('servers.%s.import.lastSync', $name), time());
}
@@ -248,17 +247,14 @@ class ImportCommand extends Command
unset($server);
$this->logger->notice(sprintf('Waiting on (%d) HTTP Requests.', count($queue)));
$this->logger->notice(sprintf('HTTP: Waiting on \'%d\' external requests.', count($queue)));
foreach ($queue as $_key => $response) {
$requestData = $response->getInfo('user_data');
try {
if (200 === $response->getStatusCode()) {
$requestData['ok']($response);
} else {
$requestData['error']($response);
}
} catch (ExceptionInterface $e) {
$requestData['ok']($response);
} catch (Throwable $e) {
$requestData['error']($e);
}
@@ -268,50 +264,40 @@ class ImportCommand extends Command
}
unset($queue);
$this->logger->notice('Finished waiting HTTP Requests.');
$this->logger->notice('HTTP: Finished waiting external requests.');
$total = count($this->mapper);
if ($total >= 1) {
$this->logger->notice(sprintf('Committing (%d) Changes.', $total));
$this->logger->notice(sprintf('MAPPER: Committing \'%d\' recorded changes.', $total));
}
$operations = $this->mapper->commit();
if ($input->getOption('stats-show')) {
Data::add('operations', 'stats', $operations);
$output->writeln(
Yaml::dump(Data::get($input->getOption('stats-filter'), []), 8, 2)
);
} else {
$a = [
[
'Type' => ucfirst(StateInterface::TYPE_MOVIE),
'Added' => $operations[StateInterface::TYPE_MOVIE]['added'] ?? 'None',
'Updated' => $operations[StateInterface::TYPE_MOVIE]['updated'] ?? 'None',
'Failed' => $operations[StateInterface::TYPE_MOVIE]['failed'] ?? 'None',
],
new TableSeparator(),
[
'Type' => ucfirst(StateInterface::TYPE_EPISODE),
'Added' => $operations[StateInterface::TYPE_EPISODE]['added'] ?? 'None',
'Updated' => $operations[StateInterface::TYPE_EPISODE]['updated'] ?? 'None',
'Failed' => $operations[StateInterface::TYPE_EPISODE]['failed'] ?? 'None',
],
];
$a = [
[
'Type' => ucfirst(StateInterface::TYPE_MOVIE),
'Added' => $operations[StateInterface::TYPE_MOVIE]['added'] ?? '-',
'Updated' => $operations[StateInterface::TYPE_MOVIE]['updated'] ?? '-',
'Failed' => $operations[StateInterface::TYPE_MOVIE]['failed'] ?? '-',
],
new TableSeparator(),
[
'Type' => ucfirst(StateInterface::TYPE_EPISODE),
'Added' => $operations[StateInterface::TYPE_EPISODE]['added'] ?? '-',
'Updated' => $operations[StateInterface::TYPE_EPISODE]['updated'] ?? '-',
'Failed' => $operations[StateInterface::TYPE_EPISODE]['failed'] ?? '-',
],
];
(new Table($output))->setHeaders(array_keys($a[0]))->setStyle('box')->setRows(array_values($a))->render();
}
(new Table($output))->setHeaders(array_keys($a[0]))->setStyle('box')->setRows(array_values($a))->render();
foreach ($list as $server) {
if (null === ($name = ag($server, 'name'))) {
continue;
}
Config::save(
sprintf('servers.%s.persist', $name),
$server['class']->getPersist()
);
Config::save(sprintf('servers.%s.persist', $name), $server['class']->getPersist());
}
if (false === $custom && is_writable(dirname($config))) {

View File

@@ -10,7 +10,7 @@ use App\Libs\Container;
use App\Libs\Data;
use App\Libs\Entity\StateInterface;
use App\Libs\Extends\CliLogger;
use App\Libs\Servers\ServerInterface;
use App\Libs\Options;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;
use Psr\SimpleCache\InvalidArgumentException;
@@ -182,7 +182,7 @@ class PushCommand extends Command
$opts = ag($server, 'server.options', []);
if ($input->getOption('ignore-date')) {
$opts[ServerInterface::OPT_EXPORT_IGNORE_DATE] = true;
$opts[Options::IGNORE_DATE] = true;
}
if ($input->getOption('proxy')) {

View File

@@ -7,6 +7,7 @@ namespace App\Libs\Mappers\Import;
use App\Libs\Data;
use App\Libs\Entity\StateInterface;
use App\Libs\Mappers\ImportInterface;
use App\Libs\Options;
use App\Libs\Storage\StorageInterface;
use DateTimeInterface;
use PDOException;
@@ -76,7 +77,7 @@ final class MemoryMapper implements ImportInterface
Data::increment($bucket, $entity->type . '_added');
$this->addPointers($this->objects[$pointer], $pointer);
if (true === ($this->options[ImportInterface::DEEP_DEBUG] ?? false)) {
if ($this->inDeepDebugMode()) {
$data = $entity->getAll();
unset($data['id']);
$data['updated'] = makeDate($data['updated']);
@@ -110,7 +111,7 @@ final class MemoryMapper implements ImportInterface
$this->removePointers($cloned);
$this->addPointers($this->objects[$pointer], $pointer);
$this->logger->info(
sprintf('Updating %s. State changed.', $name),
sprintf('%s: Updating \'%s\'. State changed.', $entity->via, $entity->getName()),
$this->objects[$pointer]->diff(all: true),
);
return $this;
@@ -163,17 +164,19 @@ final class MemoryMapper implements ImportInterface
0 === $count ? 'No changes detected.' : sprintf('Updating backend with \'%d\' changes.', $count)
);
$inDryRunMode = $this->inDryRunMode();
foreach ($this->changed as $pointer) {
try {
$entity = &$this->objects[$pointer];
if (null === $entity->id) {
if (false === (bool)($this->options[ImportInterface::DRY_RUN] ?? false)) {
if (false === $inDryRunMode) {
$storage->insert($entity);
}
$list[$entity->type]['added']++;
} else {
if (false === (bool)($this->options[ImportInterface::DRY_RUN] ?? false)) {
if (false === $inDryRunMode) {
$storage->update($entity);
}
$list[$entity->type]['updated']++;
@@ -242,12 +245,12 @@ final class MemoryMapper implements ImportInterface
public function inDryRunMode(): bool
{
return true === ($this->options[ImportInterface::DRY_RUN] ?? false);
return true === (bool)ag($this->options, Options::DRY_RUN, false);
}
public function inDeepDebugMode(): bool
{
return true === ($this->options[ImportInterface::DEEP_DEBUG] ?? false);
return true === (bool)ag($this->options, Options::DEEP_DEBUG, false);
}
/**

17
src/Libs/Options.php Normal file
View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace App\Libs;
final class Options
{
public const DRY_RUN = 'DRY_RUN';
public const FORCE_FULL = 'FORCE_FULL';
public const DEEP_DEBUG = 'DEEP_DEBUG';
public const IGNORE_DATE = 'IGNORE_DATE';
private function __construct()
{
}
}

View File

@@ -168,10 +168,10 @@ class EmbyServer extends JellyfinServer
$entity = Container::get(StateInterface::class)::fromArray($row)->setIsTainted($isTainted);
if (!$entity->hasGuids() && !$entity->hasRelativeGuid()) {
$message = sprintf('%s: No valid/supported External ids.', self::NAME);
$message = sprintf('%s: No valid/supported external ids.', self::NAME);
if (empty($providersId)) {
$message .= ' Most likely unmatched movie/episode or show.';
$message .= sprintf(' Most likely unmatched %s.', $entity->type);
}
$message .= sprintf(' [%s].', arrayToString(['guids' => !empty($providersId) ? $providersId : 'None']));
@@ -212,9 +212,9 @@ class EmbyServer extends JellyfinServer
}
$json = json_decode(
json: $response->getContent(),
json: $response->getContent(),
associative: true,
flags: JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_IGNORE
flags: JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_IGNORE
);
if (null === ($itemType = ag($json, 'Type')) || 'Series' !== $itemType) {

View File

@@ -13,6 +13,7 @@ use App\Libs\Guid;
use App\Libs\HttpException;
use App\Libs\Mappers\ExportInterface;
use App\Libs\Mappers\ImportInterface;
use App\Libs\Options;
use Closure;
use DateInterval;
use DateTimeInterface;
@@ -352,7 +353,7 @@ class JellyfinServer implements ServerInterface
$message = sprintf('%s: No valid/supported External ids.', self::NAME);
if (empty($providersId)) {
$message .= ' Most likely unmatched movie/episode or show.';
$message .= sprintf(' Most likely unmatched %s.', $entity->type);
}
$message .= sprintf(' [%s].', arrayToString(['guids' => !empty($providersId) ? $providersId : 'None']));
@@ -617,7 +618,7 @@ class JellyfinServer implements ServerInterface
continue;
}
if (false === (ag($this->options, ServerInterface::OPT_EXPORT_IGNORE_DATE, false))) {
if (false === (ag($this->options, Options::IGNORE_DATE, false))) {
if (null !== $after && $after->getTimestamp() > $entity->updated) {
continue;
}
@@ -719,7 +720,7 @@ class JellyfinServer implements ServerInterface
continue;
}
if (false === (ag($this->options, ServerInterface::OPT_EXPORT_IGNORE_DATE, false))) {
if (false === (ag($this->options, Options::IGNORE_DATE, false))) {
$date = ag($json, ['UserData.LastPlayedDate', 'DateCreated', 'PremiereDate'], null);
if (null === $date) {
@@ -1296,10 +1297,23 @@ class JellyfinServer implements ServerInterface
$message = sprintf('%s: Ignoring \'%s\'. No valid/supported external ids.', $this->name, $iName);
if (empty($providerIds)) {
$message .= ' Most likely unmatched item.';
$message .= sprintf(' Most likely unmatched %s.', $entity->type);
}
$this->logger->info($message, ['guids' => !empty($providerIds) ? $providerIds : 'None']);
$kvStore = [
'guids' => !empty($providerIds) ? $providerIds : 'None'
];
if (true === (bool)ag($this->options, Options::DEEP_DEBUG, false)) {
$kvStore['entity'] = $entity->getAll();
$kvStore['payload'] = json_decode(
json: json_encode($item),
associative: true,
flags: JSON_INVALID_UTF8_IGNORE
);
}
$this->logger->info($message, $kvStore);
Data::increment($this->name, $type . '_ignored_no_supported_guid');
@@ -1390,7 +1404,7 @@ class JellyfinServer implements ServerInterface
$message = sprintf('%s: Ignoring \'%s\'. No valid/supported external ids.', $this->name, $iName);
if (empty($providerIds)) {
$message .= ' Most likely unmatched item.';
$message .= sprintf(' Most likely unmatched %s.', $rItem->type);
}
$this->logger->debug($message, ['guids' => !empty($providerIds) ? $providerIds : 'None']);
@@ -1398,7 +1412,7 @@ class JellyfinServer implements ServerInterface
return;
}
if (false === ag($this->options, ServerInterface::OPT_EXPORT_IGNORE_DATE, false)) {
if (false === ag($this->options, Options::IGNORE_DATE, false)) {
if (null !== $after && $rItem->updated >= $after->getTimestamp()) {
$this->logger->debug(
sprintf(
@@ -1438,7 +1452,7 @@ class JellyfinServer implements ServerInterface
return;
}
if (false === ag($this->options, ServerInterface::OPT_EXPORT_IGNORE_DATE, false)) {
if (false === ag($this->options, Options::IGNORE_DATE, false)) {
if ($rItem->updated >= $entity->updated) {
$this->logger->debug(
sprintf('%s: Ignoring \'%s\'. Date is newer or equal to backend entity.', $this->name, $iName),

View File

@@ -13,6 +13,7 @@ use App\Libs\Guid;
use App\Libs\HttpException;
use App\Libs\Mappers\ExportInterface;
use App\Libs\Mappers\ImportInterface;
use App\Libs\Options;
use Closure;
use DateInterval;
use DateTimeInterface;
@@ -383,7 +384,7 @@ class PlexServer implements ServerInterface
$message = sprintf('%s: No valid/supported external ids.', self::NAME);
if (empty($item['Guid'])) {
$message .= ' Most likely unmatched movie/episode or show.';
$message .= sprintf(' Most likely unmatched %s.', $entity->type);
}
$message .= sprintf(' [%s].', arrayToString(['guids' => ag($item, 'Guid', 'None')]));
@@ -651,7 +652,7 @@ class PlexServer implements ServerInterface
continue;
}
if (false === (ag($this->options, ServerInterface::OPT_EXPORT_IGNORE_DATE, false))) {
if (false === (ag($this->options, Options::IGNORE_DATE, false))) {
if (null !== $after && $after->getTimestamp() > $entity->updated) {
continue;
}
@@ -747,7 +748,7 @@ class PlexServer implements ServerInterface
continue;
}
if (false === (ag($this->options, ServerInterface::OPT_EXPORT_IGNORE_DATE, false))) {
if (false === (ag($this->options, Options::IGNORE_DATE, false))) {
$date = max(
(int)ag($json, 'updatedAt', 0),
(int)ag($json, 'lastViewedAt', 0),
@@ -965,7 +966,7 @@ class PlexServer implements ServerInterface
$this->processCache($entity, $type, $cName);
}
} catch (PathNotFoundException $e) {
} catch (PathNotFoundException $e) {
$this->logger->error(
sprintf(
'%s: Failed to find items in \'%s\' response. %s',
@@ -1297,12 +1298,6 @@ class PlexServer implements ServerInterface
$entity = $this->createEntity($item, $type);
if (!$entity->hasGuids() && !$entity->hasRelativeGuid()) {
if (null === ($item->Guid ?? null)) {
$item->Guid = [['id' => $item->guid]];
} else {
$item->Guid[] = ['id' => $item->guid];
}
if (true === Config::get('debug.import')) {
$name = Config::get('tmpDir') . '/debug/' . $this->name . '.' . $item->ratingKey . '.json';
@@ -1320,7 +1315,13 @@ class PlexServer implements ServerInterface
$message = sprintf('%s: Ignoring \'%s\'. No valid/supported external ids.', $this->name, $iName);
if (empty($item->Guid)) {
$message .= ' Most likely unmatched item.';
$message .= sprintf(' Most likely unmatched %s.', $entity->type);
}
if (null === ($item->Guid ?? null)) {
$item->Guid = [['id' => $item->guid]];
} else {
$item->Guid[] = ['id' => $item->guid];
}
$this->logger->info($message, ['guids' => !empty($item->Guid) ? $item->Guid : 'None']);
@@ -1408,25 +1409,25 @@ class PlexServer implements ServerInterface
$rItem = $this->createEntity($item, $type);
if (!$rItem->hasGuids() && !$rItem->hasRelativeGuid()) {
$message = sprintf('%s: Ignoring \'%s\'. No valid/supported external ids.', $this->name, $iName);
if (empty($item->Guid)) {
$message .= sprintf(' Most likely unmatched %s.', $rItem->type);
}
if (null === ($item->Guid ?? null)) {
$item->Guid = [['id' => $item->guid]];
} else {
$item->Guid[] = ['id' => $item->guid];
}
$message = sprintf('%s: Ignoring \'%s\'. No valid/supported external ids.', $this->name, $iName);
if (empty($item->Guid)) {
$message .= ' Most likely unmatched item.';
}
$this->logger->debug($message, ['guids' => !empty($item->Guid) ? $item->Guid : 'None']);
Data::increment($this->name, $type . '_ignored_no_supported_guid');
return;
}
if (false === ag($this->options, ServerInterface::OPT_EXPORT_IGNORE_DATE, false)) {
if (false === ag($this->options, Options::IGNORE_DATE, false)) {
if (null !== $after && $rItem->updated >= $after->getTimestamp()) {
$this->logger->debug(
sprintf(
@@ -1466,7 +1467,7 @@ class PlexServer implements ServerInterface
return;
}
if (false === ag($this->options, ServerInterface::OPT_EXPORT_IGNORE_DATE, false)) {
if (false === ag($this->options, Options::IGNORE_DATE, false)) {
if ($rItem->updated >= $entity->updated) {
$this->logger->debug(
sprintf('%s: Ignoring \'%s\'. Date is newer or equal to backend entity.', $this->name, $iName),

View File

@@ -17,8 +17,6 @@ use Symfony\Contracts\HttpClient\ResponseInterface;
interface ServerInterface
{
public const OPT_EXPORT_IGNORE_DATE = 'exportIgnoreDate';
/**
* Initiate server. It should return **NEW OBJECT**
*