Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@@ -3,8 +3,7 @@ name: Build Container Images
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'dev'
|
||||
- '*'
|
||||
tags-ignore:
|
||||
- 'v0*'
|
||||
paths-ignore:
|
||||
|
||||
@@ -31,6 +31,8 @@ return (function () {
|
||||
'export' => [
|
||||
// -- Trigger full export mode if changes exceed X number.
|
||||
'threshold' => env('WS_EXPORT_THRESHOLD', 1000),
|
||||
// -- Extra margin for marking item not found for backend in export mode. Default 3 days.
|
||||
'not_found' => env('WS_EXPORT_NOT_FOUND', 259_200),
|
||||
],
|
||||
'episodes' => [
|
||||
'disable' => [
|
||||
|
||||
9
src/Backends/Emby/Action/Export.php
Normal file
9
src/Backends/Emby/Action/Export.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Backends\Emby\Action;
|
||||
|
||||
class Export extends \App\Backends\Jellyfin\Action\Export
|
||||
{
|
||||
}
|
||||
9
src/Backends/Emby/Action/GetLibrary.php
Normal file
9
src/Backends/Emby/Action/GetLibrary.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Backends\Emby\Action;
|
||||
|
||||
class GetLibrary extends \App\Backends\Jellyfin\Action\GetLibrary
|
||||
{
|
||||
}
|
||||
9
src/Backends/Emby/Action/Import.php
Normal file
9
src/Backends/Emby/Action/Import.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Backends\Emby\Action;
|
||||
|
||||
class Import extends \App\Backends\Jellyfin\Action\Import
|
||||
{
|
||||
}
|
||||
@@ -4,8 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Backends\Emby;
|
||||
|
||||
use App\Backends\Jellyfin\JellyfinGuid;
|
||||
|
||||
final class EmbyGuid extends JellyfinGuid
|
||||
final class EmbyGuid extends \App\Backends\Jellyfin\JellyfinGuid
|
||||
{
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ declare(strict_types=1);
|
||||
namespace App\Backends\Jellyfin\Action;
|
||||
|
||||
use App\Backends\Common\Context;
|
||||
use App\Backends\Common\GuidInterface;
|
||||
use App\Backends\Jellyfin\JellyfinClient;
|
||||
use App\Backends\Common\GuidInterface as iGuid;
|
||||
use App\Backends\Jellyfin\JellyfinClient as JFC;
|
||||
use App\Libs\Container;
|
||||
use App\Libs\Data;
|
||||
use App\Libs\Mappers\ImportInterface;
|
||||
use App\Libs\Mappers\ImportInterface as iImport;
|
||||
use App\Libs\Message;
|
||||
use App\Libs\Options;
|
||||
use App\Libs\QueueRequests;
|
||||
use DateTimeInterface;
|
||||
@@ -19,32 +19,34 @@ class Export extends Import
|
||||
{
|
||||
protected function process(
|
||||
Context $context,
|
||||
GuidInterface $guid,
|
||||
ImportInterface $mapper,
|
||||
iGuid $guid,
|
||||
iImport $mapper,
|
||||
array $item,
|
||||
array $logContext = [],
|
||||
array $opts = [],
|
||||
): void {
|
||||
if (JellyfinClient::TYPE_SHOW === ($type = ag($item, 'Type'))) {
|
||||
if (JFC::TYPE_SHOW === ($type = ag($item, 'Type'))) {
|
||||
$this->processShow(context: $context, guid: $guid, item: $item, logContext: $logContext);
|
||||
return;
|
||||
}
|
||||
|
||||
$mappedType = JFC::TYPE_MAPPER[$type] ?? $type;
|
||||
|
||||
try {
|
||||
$queue = ag($opts, 'queue', fn() => Container::get(QueueRequests::class));
|
||||
$after = ag($opts, 'after', null);
|
||||
|
||||
Data::increment($context->backendName, $type . '_total');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.total");
|
||||
|
||||
$logContext['item'] = [
|
||||
'id' => ag($item, 'Id'),
|
||||
'title' => match ($type) {
|
||||
JellyfinClient::TYPE_MOVIE => sprintf(
|
||||
JFC::TYPE_MOVIE => sprintf(
|
||||
'%s (%d)',
|
||||
ag($item, ['Name', 'OriginalTitle'], '??'),
|
||||
ag($item, 'ProductionYear', '0000')
|
||||
),
|
||||
JellyfinClient::TYPE_EPISODE => trim(
|
||||
JFC::TYPE_EPISODE => trim(
|
||||
sprintf(
|
||||
'%s - (%sx%s)',
|
||||
ag($item, 'SeriesName', '??'),
|
||||
@@ -79,7 +81,7 @@ class Export extends Import
|
||||
],
|
||||
]);
|
||||
|
||||
Data::increment($context->backendName, $type . '_ignored_no_date_is_set');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.ignored_no_date_is_set");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -107,7 +109,7 @@ class Export extends Import
|
||||
],
|
||||
]);
|
||||
|
||||
Data::increment($context->backendName, $type . '_ignored_no_supported_guid');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.ignored_no_supported_guid");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -125,7 +127,7 @@ class Export extends Import
|
||||
]
|
||||
);
|
||||
|
||||
Data::increment($context->backendName, $type . '_ignored_date_is_equal_or_higher');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.ignored_date_is_equal_or_higher");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -135,7 +137,7 @@ class Export extends Import
|
||||
'backend' => $context->backendName,
|
||||
...$logContext,
|
||||
]);
|
||||
Data::increment($context->backendName, $type . '_ignored_not_found_in_db');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.ignored_not_found_in_db");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,7 +156,7 @@ class Export extends Import
|
||||
);
|
||||
}
|
||||
|
||||
Data::increment($context->backendName, $type . '_ignored_state_unchanged');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.ignored_state_unchanged");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -171,7 +173,7 @@ class Export extends Import
|
||||
]
|
||||
);
|
||||
|
||||
Data::increment($context->backendName, $type . '_ignored_date_is_newer');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.ignored_date_is_newer");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@ use App\Backends\Common\GuidInterface as iGuid;
|
||||
use App\Backends\Common\Response;
|
||||
use App\Backends\Jellyfin\JellyfinActionTrait;
|
||||
use App\Backends\Jellyfin\JellyfinClient as JFC;
|
||||
use App\Libs\Data;
|
||||
use App\Libs\Entity\StateInterface as iFace;
|
||||
use App\Libs\Entity\StateInterface as iState;
|
||||
use App\Libs\Guid;
|
||||
use App\Libs\Mappers\ImportInterface;
|
||||
use App\Libs\Mappers\ImportInterface as iImport;
|
||||
use App\Libs\Message;
|
||||
use App\Libs\Options;
|
||||
use Closure;
|
||||
use DateTimeInterface;
|
||||
use DateTimeInterface as iDate;
|
||||
use JsonException;
|
||||
use JsonMachine\Items;
|
||||
use JsonMachine\JsonDecoder\DecodingError;
|
||||
@@ -40,8 +40,8 @@ class Import
|
||||
/**
|
||||
* @param Context $context
|
||||
* @param iGuid $guid
|
||||
* @param ImportInterface $mapper
|
||||
* @param DateTimeInterface|null $after
|
||||
* @param iImport $mapper
|
||||
* @param iDate|null $after
|
||||
* @param array $opts
|
||||
*
|
||||
* @return Response
|
||||
@@ -49,8 +49,8 @@ class Import
|
||||
public function __invoke(
|
||||
Context $context,
|
||||
iGuid $guid,
|
||||
ImportInterface $mapper,
|
||||
DateTimeInterface|null $after = null,
|
||||
iImport $mapper,
|
||||
iDate|null $after = null,
|
||||
array $opts = []
|
||||
): Response {
|
||||
return $this->tryResponse($context, fn() => $this->getLibraries(
|
||||
@@ -104,7 +104,7 @@ class Import
|
||||
'status_code' => $response->getStatusCode(),
|
||||
]
|
||||
);
|
||||
Data::add($context->backendName, 'has_errors', true);
|
||||
Message::add("{$context->backendName}.has_errors", true);
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ class Import
|
||||
'backend' => $context->backendName,
|
||||
'body' => $json,
|
||||
]);
|
||||
Data::add($context->backendName, 'has_errors', true);
|
||||
Message::add("{$context->backendName}.has_errors", true);
|
||||
return [];
|
||||
}
|
||||
} catch (ExceptionInterface $e) {
|
||||
@@ -135,10 +135,11 @@ class Import
|
||||
'trace' => $context->trace ? $e->getTrace() : [],
|
||||
],
|
||||
]);
|
||||
Data::add($context->backendName, 'has_errors', true);
|
||||
Message::add("{$context->backendName}.has_errors", true);
|
||||
return [];
|
||||
} catch (JsonException $e) {
|
||||
$this->logger->error('Request for [%(backend)] libraries returned with invalid body.', [
|
||||
'backend' => $context->backendName,
|
||||
'exception' => [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
@@ -146,7 +147,7 @@ class Import
|
||||
'trace' => $context->trace ? $e->getTrace() : [],
|
||||
],
|
||||
]);
|
||||
Data::add($context->backendName, 'has_errors', true);
|
||||
Message::add("{$context->backendName}.has_errors", true);
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -312,7 +313,7 @@ class Import
|
||||
'unsupported' => $unsupported,
|
||||
],
|
||||
]);
|
||||
Data::add($context->backendName, 'has_errors', true);
|
||||
Message::add("{$context->backendName}.has_errors", true);
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -404,6 +405,8 @@ class Import
|
||||
'duration' => number_format($end->getTimestamp() - $start->getTimestamp()),
|
||||
],
|
||||
]);
|
||||
|
||||
Message::increment('response.size', (int)$response->getInfo('size_download'));
|
||||
}
|
||||
|
||||
protected function processShow(Context $context, iGuid $guid, array $item, array $logContext = []): void
|
||||
@@ -460,7 +463,7 @@ class Import
|
||||
protected function process(
|
||||
Context $context,
|
||||
iGuid $guid,
|
||||
ImportInterface $mapper,
|
||||
iImport $mapper,
|
||||
array $item,
|
||||
array $logContext = [],
|
||||
array $opts = []
|
||||
@@ -470,8 +473,10 @@ class Import
|
||||
return;
|
||||
}
|
||||
|
||||
$mappedType = JFC::TYPE_MAPPER[$type] ?? $type;
|
||||
|
||||
try {
|
||||
Data::increment($context->backendName, $type . '_total');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.total");
|
||||
|
||||
$logContext['item'] = [
|
||||
'id' => ag($item, 'Id'),
|
||||
@@ -513,7 +518,7 @@ class Import
|
||||
'body' => $item,
|
||||
]);
|
||||
|
||||
Data::increment($context->backendName, $type . '_ignored_no_date_is_set');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.ignored_no_date_is_set");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -524,10 +529,10 @@ class Import
|
||||
opts: $opts + [
|
||||
'library' => ag($logContext, 'library.id'),
|
||||
'override' => [
|
||||
iFace::COLUMN_EXTRA => [
|
||||
iState::COLUMN_EXTRA => [
|
||||
$context->backendName => [
|
||||
iFace::COLUMN_EXTRA_EVENT => 'task.import',
|
||||
iFace::COLUMN_EXTRA_DATE => makeDate('now'),
|
||||
iState::COLUMN_EXTRA_EVENT => 'task.import',
|
||||
iState::COLUMN_EXTRA_DATE => makeDate('now'),
|
||||
],
|
||||
],
|
||||
]
|
||||
@@ -549,12 +554,12 @@ class Import
|
||||
'guids' => !empty($providerIds) ? $providerIds : 'None'
|
||||
]);
|
||||
|
||||
Data::increment($context->backendName, $type . '_ignored_no_supported_guid');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.ignored_no_supported_guid");
|
||||
return;
|
||||
}
|
||||
|
||||
$mapper->add(entity: $entity, opts: [
|
||||
'after' => ag($opts, 'after'),
|
||||
'after' => ag($opts, 'after', null),
|
||||
Options::IMPORT_METADATA_ONLY => true === (bool)ag($context->options, Options::IMPORT_METADATA_ONLY),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
|
||||
@@ -5,10 +5,9 @@ declare(strict_types=1);
|
||||
namespace App\Backends\Jellyfin\Action;
|
||||
|
||||
use App\Backends\Common\CommonTrait;
|
||||
use App\Backends\Common\Response;
|
||||
use App\Backends\Common\Context;
|
||||
use App\Backends\Common\Response;
|
||||
use App\Backends\Jellyfin\JellyfinClient;
|
||||
use App\Libs\Entity\StateInterface as iFace;
|
||||
use App\Libs\Entity\StateInterface as iState;
|
||||
use App\Libs\Options;
|
||||
use App\Libs\QueueRequests;
|
||||
@@ -52,7 +51,7 @@ class Push
|
||||
$requests = [];
|
||||
|
||||
foreach ($entities as $key => $entity) {
|
||||
if (true !== ($entity instanceof iFace)) {
|
||||
if (true !== ($entity instanceof iState)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -72,7 +71,7 @@ class Push
|
||||
],
|
||||
];
|
||||
|
||||
if (null === ag($metadata, iFace::COLUMN_ID, null)) {
|
||||
if (null === ag($metadata, iState::COLUMN_ID, null)) {
|
||||
$this->logger->warning(
|
||||
'Ignoring [%(item.title)] for [%(backend)]. No metadata was found.',
|
||||
[
|
||||
@@ -83,11 +82,11 @@ class Push
|
||||
continue;
|
||||
}
|
||||
|
||||
$logContext['remote']['id'] = ag($metadata, iFace::COLUMN_ID);
|
||||
$logContext['remote']['id'] = ag($metadata, iState::COLUMN_ID);
|
||||
|
||||
try {
|
||||
$url = $context->backendUrl->withPath(
|
||||
sprintf('/Users/%s/items/%s', $context->backendUser, ag($metadata, iFace::COLUMN_ID))
|
||||
sprintf('/Users/%s/items/%s', $context->backendUser, ag($metadata, iState::COLUMN_ID))
|
||||
)->withQuery(
|
||||
http_build_query(
|
||||
[
|
||||
@@ -149,7 +148,7 @@ class Push
|
||||
|
||||
$entity = $entities[$id];
|
||||
|
||||
assert($entity instanceof iFace);
|
||||
assert($entity instanceof iState);
|
||||
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
if (404 === $response->getStatusCode()) {
|
||||
|
||||
@@ -79,12 +79,13 @@ class JellyfinGuid implements iGuid
|
||||
}
|
||||
|
||||
try {
|
||||
$id = ag($context, 'item.id', null);
|
||||
$type = ag($context, 'item.type', '??');
|
||||
$type = JellyfinClient::TYPE_MAPPER[$type] ?? $type;
|
||||
|
||||
if (true === isIgnoredId($this->context->backendName, $type, $key, $value)) {
|
||||
if (true === isIgnoredId($this->context->backendName, $type, $key, $value, $id)) {
|
||||
if (true === $log) {
|
||||
$this->logger->info(
|
||||
$this->logger->notice(
|
||||
'Ignoring [%(backend)] external id [%(source)] for %(item.type) [%(item.title)] as requested.',
|
||||
[
|
||||
'backend' => $this->context->backendName,
|
||||
|
||||
@@ -5,11 +5,11 @@ declare(strict_types=1);
|
||||
namespace App\Backends\Plex\Action;
|
||||
|
||||
use App\Backends\Common\Context;
|
||||
use App\Backends\Common\GuidInterface;
|
||||
use App\Backends\Common\GuidInterface as iGuid;
|
||||
use App\Backends\Plex\PlexClient;
|
||||
use App\Libs\Container;
|
||||
use App\Libs\Data;
|
||||
use App\Libs\Mappers\ImportInterface;
|
||||
use App\Libs\Mappers\ImportInterface as iImport;
|
||||
use App\Libs\Message;
|
||||
use App\Libs\Options;
|
||||
use App\Libs\QueueRequests;
|
||||
use DateTimeInterface;
|
||||
@@ -19,8 +19,8 @@ final class Export extends Import
|
||||
{
|
||||
protected function process(
|
||||
Context $context,
|
||||
GuidInterface $guid,
|
||||
ImportInterface $mapper,
|
||||
iGuid $guid,
|
||||
iImport $mapper,
|
||||
array $item,
|
||||
array $logContext = [],
|
||||
array $opts = [],
|
||||
@@ -35,9 +35,11 @@ final class Export extends Import
|
||||
return;
|
||||
}
|
||||
|
||||
$mappedType = PlexClient::TYPE_MAPPER[$type] ?? $type;
|
||||
|
||||
try {
|
||||
Data::increment($context->backendName, $library . '_total');
|
||||
Data::increment($context->backendName, $type . '_total');
|
||||
Message::increment("{$context->backendName}.{$library}.total");
|
||||
Message::increment("{$context->backendName}.{$mappedType}.total");
|
||||
|
||||
$year = (int)ag($item, ['grandParentYear', 'parentYear', 'year'], 0);
|
||||
if (0 === $year && null !== ($airDate = ag($item, 'originallyAvailableAt'))) {
|
||||
@@ -80,7 +82,7 @@ final class Export extends Import
|
||||
],
|
||||
]);
|
||||
|
||||
Data::increment($context->backendName, $type . '_ignored_no_date_is_set');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.ignored_no_date_is_set");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,7 +116,7 @@ final class Export extends Import
|
||||
],
|
||||
]);
|
||||
|
||||
Data::increment($context->backendName, $type . '_ignored_no_supported_guid');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.ignored_no_supported_guid");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -132,7 +134,7 @@ final class Export extends Import
|
||||
]
|
||||
);
|
||||
|
||||
Data::increment($context->backendName, $type . '_ignored_date_is_equal_or_higher');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.ignored_date_is_equal_or_higher");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -142,7 +144,7 @@ final class Export extends Import
|
||||
'backend' => $context->backendName,
|
||||
...$logContext,
|
||||
]);
|
||||
Data::increment($context->backendName, $type . '_ignored_not_found_in_db');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.ignored_not_found_in_db");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -161,7 +163,7 @@ final class Export extends Import
|
||||
);
|
||||
}
|
||||
|
||||
Data::increment($context->backendName, $type . '_ignored_state_unchanged');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.ignored_state_unchanged");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -178,7 +180,7 @@ final class Export extends Import
|
||||
]
|
||||
);
|
||||
|
||||
Data::increment($context->backendName, $type . '_ignored_date_is_newer');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.ignored_date_is_newer");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@ use App\Backends\Common\GuidInterface as iGuid;
|
||||
use App\Backends\Common\Response;
|
||||
use App\Backends\Plex\PlexActionTrait;
|
||||
use App\Backends\Plex\PlexClient;
|
||||
use App\Libs\Data;
|
||||
use App\Libs\Entity\StateInterface as iFace;
|
||||
use App\Libs\Entity\StateInterface as iState;
|
||||
use App\Libs\Guid;
|
||||
use App\Libs\Mappers\ImportInterface;
|
||||
use App\Libs\Mappers\ImportInterface as iImport;
|
||||
use App\Libs\Message;
|
||||
use App\Libs\Options;
|
||||
use Closure;
|
||||
use DateTimeInterface;
|
||||
use DateTimeInterface as iDate;
|
||||
use JsonException;
|
||||
use JsonMachine\Items;
|
||||
use JsonMachine\JsonDecoder\DecodingError;
|
||||
@@ -40,8 +40,8 @@ class Import
|
||||
/**
|
||||
* @param Context $context
|
||||
* @param iGuid $guid
|
||||
* @param ImportInterface $mapper
|
||||
* @param DateTimeInterface|null $after
|
||||
* @param iImport $mapper
|
||||
* @param iDate|null $after
|
||||
* @param array $opts
|
||||
*
|
||||
* @return Response
|
||||
@@ -49,8 +49,8 @@ class Import
|
||||
public function __invoke(
|
||||
Context $context,
|
||||
iGuid $guid,
|
||||
ImportInterface $mapper,
|
||||
DateTimeInterface|null $after = null,
|
||||
iImport $mapper,
|
||||
iDate|null $after = null,
|
||||
array $opts = []
|
||||
): Response {
|
||||
return $this->tryResponse($context, fn() => $this->getLibraries(
|
||||
@@ -105,7 +105,7 @@ class Import
|
||||
]
|
||||
);
|
||||
|
||||
Data::add($context->backendName, 'has_errors', true);
|
||||
Message::add("{$context->backendName}.has_errors", true);
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ class Import
|
||||
'backend' => $context->backendName,
|
||||
'body' => $json,
|
||||
]);
|
||||
Data::add($context->backendName, 'has_errors', true);
|
||||
Message::add("{$context->backendName}.has_errors", true);
|
||||
return [];
|
||||
}
|
||||
} catch (ExceptionInterface $e) {
|
||||
@@ -136,10 +136,11 @@ class Import
|
||||
'trace' => $context->trace ? $e->getTrace() : [],
|
||||
],
|
||||
]);
|
||||
Data::add($context->backendName, 'has_errors', true);
|
||||
Message::add("{$context->backendName}.has_errors", true);
|
||||
return [];
|
||||
} catch (JsonException $e) {
|
||||
$this->logger->error('Request for [%(backend)] libraries returned with invalid body.', [
|
||||
'backend' => $context->backendName,
|
||||
'exception' => [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
@@ -147,7 +148,7 @@ class Import
|
||||
'trace' => $context->trace ? $e->getTrace() : [],
|
||||
],
|
||||
]);
|
||||
Data::add($context->backendName, 'has_errors', true);
|
||||
Message::add("{$context->backendName}.has_errors", true);
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -336,7 +337,7 @@ class Import
|
||||
],
|
||||
]);
|
||||
|
||||
Data::add($context->backendName, 'has_errors', true);
|
||||
Message::add("{$context->backendName}.has_errors", true);
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -360,6 +361,7 @@ class Import
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$start = makeDate();
|
||||
$this->logger->info('Parsing [%(backend)] library [%(library.title)] response.', [
|
||||
'backend' => $context->backendName,
|
||||
@@ -428,6 +430,8 @@ class Import
|
||||
'duration' => number_format($end->getTimestamp() - $start->getTimestamp()),
|
||||
],
|
||||
]);
|
||||
|
||||
Message::increment('response.size', (int)$response->getInfo('size_download'));
|
||||
}
|
||||
|
||||
protected function processShow(Context $context, iGuid $guid, array $item, array $logContext = []): void
|
||||
@@ -502,23 +506,20 @@ class Import
|
||||
protected function process(
|
||||
Context $context,
|
||||
iGuid $guid,
|
||||
ImportInterface $mapper,
|
||||
iImport $mapper,
|
||||
array $item,
|
||||
array $logContext = [],
|
||||
array $opts = []
|
||||
): void {
|
||||
$after = ag($opts, 'after', null);
|
||||
$library = ag($logContext, 'library.id');
|
||||
$type = ag($item, 'type');
|
||||
if (PlexClient::TYPE_SHOW === ($type = ag($item, 'type'))) {
|
||||
$this->processShow(context: $context, guid: $guid, item: $item, logContext: $logContext);
|
||||
return;
|
||||
}
|
||||
|
||||
$mappedType = PlexClient::TYPE_MAPPER[$type] ?? $type;
|
||||
|
||||
try {
|
||||
if (PlexClient::TYPE_SHOW === $type) {
|
||||
$this->processShow($context, $guid, $item, $logContext);
|
||||
return;
|
||||
}
|
||||
|
||||
Data::increment($context->backendName, $library . '_total');
|
||||
Data::increment($context->backendName, $type . '_total');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.total");
|
||||
|
||||
$year = (int)ag($item, ['grandParentYear', 'parentYear', 'year'], 0);
|
||||
if (0 === $year && null !== ($airDate = ag($item, 'originallyAvailableAt'))) {
|
||||
@@ -559,7 +560,7 @@ class Import
|
||||
'body' => $item,
|
||||
]);
|
||||
|
||||
Data::increment($context->backendName, $type . '_ignored_no_date_is_set');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.ignored_no_date_is_set");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -569,10 +570,10 @@ class Import
|
||||
item: $item,
|
||||
opts: $opts + [
|
||||
'override' => [
|
||||
iFace::COLUMN_EXTRA => [
|
||||
iState::COLUMN_EXTRA => [
|
||||
$context->backendName => [
|
||||
iFace::COLUMN_EXTRA_EVENT => 'task.import',
|
||||
iFace::COLUMN_EXTRA_DATE => makeDate('now'),
|
||||
iState::COLUMN_EXTRA_EVENT => 'task.import',
|
||||
iState::COLUMN_EXTRA_DATE => makeDate('now'),
|
||||
],
|
||||
],
|
||||
],
|
||||
@@ -600,12 +601,12 @@ class Import
|
||||
'guids' => !empty($item['Guid']) ? $item['Guid'] : 'None'
|
||||
]);
|
||||
|
||||
Data::increment($context->backendName, $type . '_ignored_no_supported_guid');
|
||||
Message::increment("{$context->backendName}.{$mappedType}.ignored_no_supported_guid");
|
||||
return;
|
||||
}
|
||||
|
||||
$mapper->add(entity: $entity, opts: [
|
||||
'after' => $after,
|
||||
'after' => ag($opts, 'after', null),
|
||||
Options::IMPORT_METADATA_ONLY => true === (bool)ag($context->options, Options::IMPORT_METADATA_ONLY),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
|
||||
@@ -140,12 +140,13 @@ final class PlexGuid implements GuidInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
$id = ag($context, 'item.id', null);
|
||||
$type = ag($context, 'item.type', '??');
|
||||
$type = PlexClient::TYPE_MAPPER[$type] ?? $type;
|
||||
|
||||
if (true === isIgnoredId($this->context->backendName, $type, $key, $value)) {
|
||||
if (true === isIgnoredId($this->context->backendName, $type, $key, $value, $id)) {
|
||||
if (true === $log) {
|
||||
$this->logger->info(
|
||||
$this->logger->notice(
|
||||
'Ignoring [%(backend)] external id [%(source)] for %(item.type) [%(item.title)] as requested.',
|
||||
[
|
||||
'backend' => $this->context->backendName,
|
||||
|
||||
@@ -255,7 +255,7 @@ class Command extends BaseCommand
|
||||
|
||||
$suggest = [];
|
||||
|
||||
foreach (self::DISPLAY_OUTPUT as $name) {
|
||||
foreach (static::DISPLAY_OUTPUT as $name) {
|
||||
if (empty($currentValue) || str_starts_with($name, $currentValue)) {
|
||||
$suggest[] = $name;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,14 @@ namespace App\Commands\Backend\Ignore;
|
||||
|
||||
use App\Command;
|
||||
use App\Libs\Config;
|
||||
use App\Libs\Entity\StateInterface as iFace;
|
||||
use App\Libs\Container;
|
||||
use App\Libs\Entity\StateInterface as iState;
|
||||
use App\Libs\Guid;
|
||||
use App\Libs\Storage\StorageInterface;
|
||||
use PDO;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Psr\SimpleCache\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
@@ -16,6 +22,29 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
final class ListCommand extends Command
|
||||
{
|
||||
private const CACHE_KEY = 'ignorelist_titles';
|
||||
|
||||
private array $cache = [];
|
||||
|
||||
private PDO $db;
|
||||
private CacheInterface $cacheIO;
|
||||
|
||||
public function __construct(StorageInterface $storage, CacheInterface $cacheIO)
|
||||
{
|
||||
$this->cacheIO = $cacheIO;
|
||||
$this->db = $storage->getPdo();
|
||||
|
||||
try {
|
||||
if ($this->cacheIO->has(self::CACHE_KEY)) {
|
||||
$this->cache = $this->cacheIO->get(self::CACHE_KEY);
|
||||
}
|
||||
} catch (InvalidArgumentException) {
|
||||
$this->cache = [];
|
||||
}
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$cmdContext = trim(commandContext());
|
||||
@@ -25,6 +54,7 @@ final class ListCommand extends Command
|
||||
->addOption('backend', null, InputOption::VALUE_REQUIRED, 'Filter based on backend.')
|
||||
->addOption('db', null, InputOption::VALUE_REQUIRED, 'Filter based on db.')
|
||||
->addOption('id', null, InputOption::VALUE_REQUIRED, 'Filter based on id.')
|
||||
->addOption('with-title', null, InputOption::VALUE_NONE, 'Include entity title in response. Slow operation')
|
||||
->setDescription('List Ignored external ids.')
|
||||
->setHelp(
|
||||
<<<HELP
|
||||
@@ -48,6 +78,12 @@ HELP
|
||||
|
||||
protected function runCommand(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$path = Config::get('path') . '/config/ignore.yaml';
|
||||
|
||||
if (false === file_exists($path)) {
|
||||
touch($path);
|
||||
}
|
||||
|
||||
$list = [];
|
||||
|
||||
$fBackend = $input->getOption('backend');
|
||||
@@ -64,6 +100,7 @@ HELP
|
||||
$type = ag($urlParts, 'scheme');
|
||||
$db = ag($urlParts, 'user');
|
||||
$id = ag($urlParts, 'pass');
|
||||
$scope = ag($urlParts, 'query');
|
||||
|
||||
if (null !== $fBackend && $backend !== $fBackend) {
|
||||
continue;
|
||||
@@ -81,21 +118,41 @@ HELP
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[] = [
|
||||
$rule = makeIgnoreId($guid);
|
||||
|
||||
$builder = [
|
||||
'type' => ucfirst($type),
|
||||
'backend' => $backend,
|
||||
'type' => $type,
|
||||
'db' => $db,
|
||||
'id' => $id,
|
||||
'created' => makeDate($date),
|
||||
'Scoped' => null === $scope ? 'No' : 'Yes',
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($this->cache) || $input->getOption('with-title')) {
|
||||
$builder['title'] = null !== $scope ? ($this->getinfo($rule) ?? 'Unknown') : '** Global Rule **';
|
||||
}
|
||||
|
||||
if ('table' !== $input->getOption('output')) {
|
||||
$builder = ['rule' => (string)$rule] + $builder;
|
||||
$builder['scope'] = [];
|
||||
if (null !== $scope) {
|
||||
parse_str($scope, $builder['scope']);
|
||||
}
|
||||
$builder['created'] = makeDate($date);
|
||||
} else {
|
||||
$builder['created'] = makeDate($date)->format('Y-m-d H:i:s T');
|
||||
}
|
||||
$list[] = $builder;
|
||||
}
|
||||
|
||||
if (empty($list)) {
|
||||
$hasIds = count($ids) >= 1;
|
||||
|
||||
$output->writeln(
|
||||
$hasIds ? '<comment>Filters did not return any results.</comment>' : '<info>Ignore list is empty.</info>'
|
||||
);
|
||||
if ($hasIds) {
|
||||
|
||||
if (true === $hasIds) {
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
@@ -105,6 +162,55 @@ HELP
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function getInfo(UriInterface $uri): string|null
|
||||
{
|
||||
if (empty($uri->getQuery())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$params = [];
|
||||
parse_str($uri->getQuery(), $params);
|
||||
|
||||
$key = sprintf('%s://%s@%s', $uri->getScheme(), $uri->getHost(), $params['id']);
|
||||
|
||||
if (true === array_key_exists($key, $this->cache)) {
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
$sql = sprintf(
|
||||
"SELECT * FROM state WHERE JSON_EXTRACT(metadata, '$.%s.%s') = :id LIMIT 1",
|
||||
$uri->getHost(),
|
||||
$uri->getScheme() === iState::TYPE_SHOW ? 'show' : 'id'
|
||||
);
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute(['id' => $params['id']]);
|
||||
$item = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($item)) {
|
||||
$this->cache[$key] = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->cache[$key] = Container::get(iState::class)->fromArray($item)->getName(
|
||||
iState::TYPE_SHOW === $uri->getScheme()
|
||||
);
|
||||
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (empty($this->cache)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->cacheIO->set(self::CACHE_KEY, $this->cache, new \DateInterval('P3D'));
|
||||
} catch (InvalidArgumentException) {
|
||||
}
|
||||
}
|
||||
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($input->mustSuggestOptionValuesFor('backend')) {
|
||||
@@ -125,7 +231,7 @@ HELP
|
||||
|
||||
$suggest = [];
|
||||
|
||||
foreach (iFace::TYPES_LIST as $name) {
|
||||
foreach (iState::TYPES_LIST as $name) {
|
||||
if (empty($currentValue) || str_starts_with($name, $currentValue)) {
|
||||
$suggest[] = $name;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ This command allow you to ignore specific external id from backend.
|
||||
This helps when there is a conflict between your media servers provided external ids.
|
||||
Generally this should only be used as last resort. You should try to fix the source of the problem.
|
||||
|
||||
The <info>id</info> format is: <info>type</info>://<info>db</info>:<info>id</info>@<info>backend_name</info>
|
||||
The <info>id</info> format is: <info>type</info>://<info>db</info>:<info>id</info>@<info>backend_name</info>[<info>?id=backend_id</info>]
|
||||
|
||||
-----------------------------
|
||||
<comment>How to Add id to ignore list.</comment>
|
||||
@@ -51,6 +51,14 @@ For <comment>movies</comment>:
|
||||
For <comment>episodes</comment>:
|
||||
{$cmdContext} servers:ignore <comment>episode</comment>://<info>tvdb</info>:<info>320234</info>@<info>plex_home</info>
|
||||
|
||||
To scope ignore rule to specfic item from backend, You can do the same as and add [<info>?id=backend_id</info>].
|
||||
|
||||
<comment>[backend_id]:</comment>
|
||||
|
||||
Refers to the item id from backend. To ignore a specfic guid for item id <info>1212111</info> you can do something like this:
|
||||
|
||||
{$cmdContext} servers:ignore <comment>episode</comment>://<info>tvdb</info>:<info>320234</info>@<info>plex_home</info>?id=<info>1212111</info>
|
||||
|
||||
----------------------------------
|
||||
<comment>How to Remove id from ignore list.</comment>
|
||||
----------------------------------
|
||||
@@ -95,18 +103,37 @@ HELP
|
||||
$output->writeln(sprintf('<info>Removed: id \'%s\' from ignore list.</info>', $id));
|
||||
} else {
|
||||
$this->checkGuid($id);
|
||||
if (true === ag_exists($list, $id)) {
|
||||
|
||||
$id = makeIgnoreId($id);
|
||||
|
||||
if (true === ag_exists($list, (string)$id)) {
|
||||
$output->writeln(
|
||||
sprintf(
|
||||
'<comment>Id \'%s\' already exists in the ignore list. added at \'%s\'.</comment>',
|
||||
$id,
|
||||
makeDate(ag($list, $id))->format('Y-m-d H:i:s T')
|
||||
replacer(
|
||||
'<comment>ERROR: Cannot add [{id}] as it\'s already exists. added at [{date}].</comment>',
|
||||
[
|
||||
'id' => $id,
|
||||
'date' => makeDate(ag($list, (string)$id))->format('Y-m-d H:i:s T'),
|
||||
],
|
||||
)
|
||||
);
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$list = ag_set($list, $id, makeDate());
|
||||
if (true === ag_exists($list, (string)$id->withQuery(''))) {
|
||||
$output->writeln(
|
||||
replacer(
|
||||
'<comment>ERROR: Cannot add [{id}] as [{global}] already exists. added at [{date}].</comment>',
|
||||
[
|
||||
'id' => (string)$id,
|
||||
'global' => (string)$id->withQuery(''),
|
||||
'date' => makeDate(ag($list, (string)$id->withQuery('')))->format('Y-m-d H:i:s T')
|
||||
]
|
||||
)
|
||||
);
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$list = ag_set($list, (string)$id, time());
|
||||
$output->writeln(sprintf('<info>Added: id \'%s\' to ignore list.</info>', $id));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,9 @@ namespace App\Commands\State;
|
||||
|
||||
use App\Command;
|
||||
use App\Libs\Config;
|
||||
use App\Libs\Data;
|
||||
use App\Libs\Entity\StateInterface as iState;
|
||||
use App\Libs\Mappers\Import\DirectMapper;
|
||||
use App\Libs\Message;
|
||||
use App\Libs\Options;
|
||||
use App\Libs\QueueRequests;
|
||||
use App\Libs\Storage\StorageInterface;
|
||||
@@ -142,8 +143,6 @@ class ExportCommand extends Command
|
||||
continue;
|
||||
}
|
||||
|
||||
Data::addBucket($name);
|
||||
|
||||
$opts = ag($backend, 'options', []);
|
||||
|
||||
if ($input->getOption('ignore-date')) {
|
||||
@@ -226,11 +225,35 @@ class ExportCommand extends Command
|
||||
foreach ($backends as $backend) {
|
||||
$name = ag($backend, 'name');
|
||||
|
||||
if (null === ag($backend, 'export.lastSync', null)) {
|
||||
if (null === ($lastSync = ag($backend, 'export.lastSync', null))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (false === ag_exists($entity->getMetadata(), $name)) {
|
||||
$addedDate = ag($entity->getMetadata($entity->via), iState::COLUMN_META_DATA_ADDED_AT);
|
||||
$extraMargin = (int)Config::get('export.not_found');
|
||||
|
||||
if (null !== $addedDate && $lastSync > ($addedDate + $extraMargin)) {
|
||||
$this->logger->info(
|
||||
'SYSTEM: Ignoring [%(item.title)] for [%(backend)] waiting period for metadata expired.',
|
||||
[
|
||||
'backend' => $name,
|
||||
'item' => [
|
||||
'id' => $entity->id,
|
||||
'title' => $entity->getName(),
|
||||
],
|
||||
'wait_period' => [
|
||||
'added_at' => makeDate($addedDate),
|
||||
'extra_margin' => $extraMargin,
|
||||
'last_sync_at' => makeDate($lastSync),
|
||||
'diff' => $lastSync - ($addedDate + $extraMargin),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (true === ag_exists($push, $name)) {
|
||||
unset($push[$name]);
|
||||
}
|
||||
@@ -243,6 +266,12 @@ class ExportCommand extends Command
|
||||
'id' => $entity->id,
|
||||
'title' => $entity->getName(),
|
||||
],
|
||||
'wait_period' => [
|
||||
'added_at' => makeDate($addedDate),
|
||||
'extra_margin' => $extraMargin,
|
||||
'last_sync_at' => makeDate($lastSync),
|
||||
'diff' => $lastSync - ($addedDate + $extraMargin),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
@@ -334,12 +363,15 @@ class ExportCommand extends Command
|
||||
continue;
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
||||
} else {
|
||||
if (false === (bool)Message::get("{$name}.has_errors", false)) {
|
||||
Config::save(sprintf('servers.%s.export.lastSync', $name), time());
|
||||
} else {
|
||||
$this->logger->warning(
|
||||
'SYSTEM: Not updating last export date for [%(backend)]. Backend reported an error.',
|
||||
[
|
||||
'backend' => $name,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,12 +481,12 @@ 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.has_errors', $name))) {
|
||||
$this->logger->notice('Not updating last export date. [%(backend)] report an error.', [
|
||||
if (true === (bool)Message::get("{$name}.has_errors")) {
|
||||
$this->logger->warning('SYSTEM: Not updating last export date. [%(backend)] report an error.', [
|
||||
'backend' => $name,
|
||||
]);
|
||||
} else {
|
||||
Config::save(sprintf('servers.%s.export.lastSync', $name), time());
|
||||
Config::save("servers.{$name}.export.lastSync", time());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ namespace App\Commands\State;
|
||||
|
||||
use App\Command;
|
||||
use App\Libs\Config;
|
||||
use App\Libs\Data;
|
||||
use App\Libs\Entity\StateInterface;
|
||||
use App\Libs\Entity\StateInterface as iState;
|
||||
use App\Libs\Mappers\Import\DirectMapper;
|
||||
use App\Libs\Mappers\ImportInterface;
|
||||
use App\Libs\Message;
|
||||
use App\Libs\Options;
|
||||
use App\Libs\Storage\StorageInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -65,6 +65,7 @@ class ImportCommand extends Command
|
||||
InputOption::VALUE_NONE,
|
||||
'import metadata changes only. Works when there are records in storage.'
|
||||
)
|
||||
->addOption('show-messages', null, InputOption::VALUE_NONE, 'Show internal messages.')
|
||||
->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Use Alternative config file.')
|
||||
->setAliases(['import', 'pull']);
|
||||
}
|
||||
@@ -204,7 +205,6 @@ class ImportCommand extends Command
|
||||
$this->storage->singleTransaction();
|
||||
|
||||
foreach ($list as $name => &$server) {
|
||||
Data::addBucket($name);
|
||||
$metadata = false;
|
||||
$opts = ag($server, 'options', []);
|
||||
|
||||
@@ -249,8 +249,14 @@ class ImportCommand extends Command
|
||||
|
||||
$inDryMode = $this->mapper->inDryRunMode() || ag($server, 'options.' . Options::DRY_RUN);
|
||||
|
||||
if (false === Data::get(sprintf('%s.has_errors', $name)) && false === $inDryMode) {
|
||||
Config::save(sprintf('servers.%s.import.lastSync', $name), time());
|
||||
if (false === $inDryMode) {
|
||||
if (true === (bool)Message::get("{$name}.has_errors")) {
|
||||
$this->logger->warning('SYSTEM: Not updating last import date. [%(backend)] reported an error.', [
|
||||
'backend' => $name,
|
||||
]);
|
||||
} else {
|
||||
Config::save("servers.{$name}.import.lastSync", time());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,6 +301,9 @@ class ImportCommand extends Command
|
||||
'now' => getMemoryUsage(),
|
||||
'peak' => getPeakMemoryUsage(),
|
||||
],
|
||||
'responses' => [
|
||||
'size' => fsize((int)Message::get('response.size', 0)),
|
||||
],
|
||||
]);
|
||||
|
||||
$queue = $requestData = null;
|
||||
@@ -315,17 +324,17 @@ class ImportCommand extends Command
|
||||
|
||||
$a = [
|
||||
[
|
||||
'Type' => ucfirst(StateInterface::TYPE_MOVIE),
|
||||
'Added' => $operations[StateInterface::TYPE_MOVIE]['added'] ?? '-',
|
||||
'Updated' => $operations[StateInterface::TYPE_MOVIE]['updated'] ?? '-',
|
||||
'Failed' => $operations[StateInterface::TYPE_MOVIE]['failed'] ?? '-',
|
||||
'Type' => ucfirst(iState::TYPE_MOVIE),
|
||||
'Added' => $operations[iState::TYPE_MOVIE]['added'] ?? '-',
|
||||
'Updated' => $operations[iState::TYPE_MOVIE]['updated'] ?? '-',
|
||||
'Failed' => $operations[iState::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'] ?? '-',
|
||||
'Type' => ucfirst(iState::TYPE_EPISODE),
|
||||
'Added' => $operations[iState::TYPE_EPISODE]['added'] ?? '-',
|
||||
'Updated' => $operations[iState::TYPE_EPISODE]['updated'] ?? '-',
|
||||
'Failed' => $operations[iState::TYPE_EPISODE]['failed'] ?? '-',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -339,6 +348,10 @@ class ImportCommand extends Command
|
||||
file_put_contents($config, Yaml::dump(Config::get('servers', []), 8, 2));
|
||||
}
|
||||
|
||||
if ($input->getOption('show-messages')) {
|
||||
$this->displayContent(Message::getAll(), $output, $input->getOption('output') === 'json' ? 'json' : 'yaml');
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ namespace App\Commands\State;
|
||||
use App\Command;
|
||||
use App\Libs\Config;
|
||||
use App\Libs\Container;
|
||||
use App\Libs\Data;
|
||||
use App\Libs\Entity\StateInterface;
|
||||
use App\Libs\Entity\StateInterface as iState;
|
||||
use App\Libs\Options;
|
||||
use App\Libs\QueueRequests;
|
||||
use App\Libs\Storage\StorageInterface;
|
||||
@@ -75,7 +74,7 @@ class PushCommand extends Command
|
||||
$entities = $items = [];
|
||||
|
||||
foreach ($this->cache->get('queue', []) as $item) {
|
||||
$items[] = Container::get(StateInterface::class)::fromArray($item);
|
||||
$items[] = Container::get(iState::class)::fromArray($item);
|
||||
}
|
||||
|
||||
if (!empty($items)) {
|
||||
@@ -142,7 +141,6 @@ class PushCommand extends Command
|
||||
}
|
||||
|
||||
foreach ($list as $name => &$server) {
|
||||
Data::addBucket((string)$name);
|
||||
$opts = ag($server, 'options', []);
|
||||
|
||||
if ($input->getOption('ignore-date')) {
|
||||
@@ -199,6 +197,7 @@ class PushCommand extends Command
|
||||
'line' => $e->getLine(),
|
||||
'kind' => get_class($e),
|
||||
'message' => $e->getMessage(),
|
||||
'trace' => $e->getTrace(),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Libs;
|
||||
|
||||
final class Data
|
||||
{
|
||||
private static array $data = [];
|
||||
|
||||
public static function addBucket(string $bucket): void
|
||||
{
|
||||
self::$data[$bucket] = [];
|
||||
}
|
||||
|
||||
public static function add(string $bucket, string $key, mixed $value): void
|
||||
{
|
||||
if (!isset(self::$data[$bucket])) {
|
||||
self::$data[$bucket] = [];
|
||||
}
|
||||
|
||||
self::$data[$bucket][$key] = $value;
|
||||
}
|
||||
|
||||
public static function increment(string $bucket, string $key, int $increment = 1): void
|
||||
{
|
||||
if (!isset(self::$data[$bucket])) {
|
||||
self::$data[$bucket] = [];
|
||||
}
|
||||
|
||||
self::$data[$bucket][$key] = (self::$data[$bucket][$key] ?? 0) + $increment;
|
||||
}
|
||||
|
||||
public static function append(string $bucket, string $key, mixed $value): void
|
||||
{
|
||||
if (!isset(self::$data[$bucket])) {
|
||||
self::$data[$bucket] = [];
|
||||
}
|
||||
|
||||
if (!isset(self::$data[$bucket][$key])) {
|
||||
self::$data[$bucket][$key] = [];
|
||||
}
|
||||
|
||||
if (!is_array(self::$data[$bucket][$key]) && !empty(self::$data[$bucket][$key])) {
|
||||
self::$data[$bucket][$key] = [self::$data[$bucket][$key]];
|
||||
}
|
||||
|
||||
self::$data[$bucket][$key][] = $value;
|
||||
}
|
||||
|
||||
public static function get(null|string $filter = null, mixed $default = null): mixed
|
||||
{
|
||||
return ag(self::$data, $filter, $default);
|
||||
}
|
||||
|
||||
public static function reset(): void
|
||||
{
|
||||
self::$data = [];
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Libs\Entity;
|
||||
|
||||
use App\Libs\Entity\StateInterface as iFace;
|
||||
use App\Libs\Guid;
|
||||
use RuntimeException;
|
||||
use App\Libs\Entity\StateInterface as iFace;
|
||||
|
||||
final class StateEntity implements iFace
|
||||
{
|
||||
@@ -100,12 +100,12 @@ final class StateEntity implements iFace
|
||||
return $changed;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
public function getName(bool $asMovie = false): string
|
||||
{
|
||||
$title = ag($this->data, iFace::COLUMN_TITLE, $this->title);
|
||||
$year = ag($this->data, iFace::COLUMN_YEAR, $this->year);
|
||||
|
||||
if ($this->isMovie()) {
|
||||
if ($this->isMovie() || true === $asMovie) {
|
||||
return sprintf('%s (%s)', $title, $year ?? '0000');
|
||||
}
|
||||
|
||||
|
||||
@@ -199,9 +199,11 @@ interface StateInterface
|
||||
/**
|
||||
* Get constructed name.
|
||||
*
|
||||
* @param bool $asMovie Return episode title as movie format.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string;
|
||||
public function getName(bool $asMovie = false): string;
|
||||
|
||||
/**
|
||||
* Get external ids Pointers.
|
||||
|
||||
@@ -78,9 +78,14 @@ final class Initializer
|
||||
}
|
||||
|
||||
$path = Config::get('path') . '/config/ignore.yaml';
|
||||
|
||||
if (file_exists($path)) {
|
||||
Config::save('ignore', Yaml::parseFile($path));
|
||||
if (($yaml = Yaml::parseFile($path)) && is_array($yaml)) {
|
||||
$list = [];
|
||||
foreach ($yaml as $key => $val) {
|
||||
$list[(string)makeIgnoreId($key)] = $val;
|
||||
}
|
||||
Config::save('ignore', $list);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -157,6 +162,7 @@ final class Initializer
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'kind' => get_class($e),
|
||||
'trace' => $e->getTrace(),
|
||||
]
|
||||
);
|
||||
$response = new Response(500);
|
||||
@@ -206,6 +212,7 @@ final class Initializer
|
||||
'line' => $e->getLine(),
|
||||
'kind' => get_class($e),
|
||||
'message' => $e->getMessage(),
|
||||
'trace' => $e->getTrace(),
|
||||
]
|
||||
]);
|
||||
continue;
|
||||
|
||||
@@ -5,10 +5,10 @@ declare(strict_types=1);
|
||||
namespace App\Libs\Mappers\Import;
|
||||
|
||||
use App\Libs\Container;
|
||||
use App\Libs\Data;
|
||||
use App\Libs\Entity\StateInterface as iFace;
|
||||
use App\Libs\Guid;
|
||||
use App\Libs\Mappers\ImportInterface;
|
||||
use App\Libs\Message;
|
||||
use App\Libs\Options;
|
||||
use App\Libs\Storage\StorageInterface;
|
||||
use DateTimeInterface;
|
||||
@@ -97,7 +97,7 @@ final class DirectMapper implements ImportInterface
|
||||
'backend' => $entity->via,
|
||||
'title' => $entity->getName(),
|
||||
]);
|
||||
Data::increment($entity->via, $entity->type . '_failed_no_guid');
|
||||
Message::increment("{$entity->via}.{$entity->type}.failed_no_guid");
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ final class DirectMapper implements ImportInterface
|
||||
if (null === ($local = $this->get($entity))) {
|
||||
if (true === $metadataOnly) {
|
||||
$this->actions[$entity->type]['failed']++;
|
||||
Data::increment($entity->via, $entity->type . '_failed');
|
||||
Message::increment("{$entity->via}.{$entity->type}.failed");
|
||||
|
||||
$this->logger->notice('MAPPER: Ignoring [%(backend)] [%(title)]. Does not exist in storage.', [
|
||||
'metaOnly' => true,
|
||||
@@ -161,13 +161,13 @@ final class DirectMapper implements ImportInterface
|
||||
|
||||
if (null === ($this->changed[$entity->id] ?? null)) {
|
||||
$this->actions[$entity->type]['added']++;
|
||||
Data::increment($entity->via, $entity->type . '_added');
|
||||
Message::increment("{$entity->via}.{$entity->type}.added");
|
||||
}
|
||||
|
||||
$this->changed[$entity->id] = $this->objects[$entity->id] = $entity->id;
|
||||
} catch (PDOException|Exception $e) {
|
||||
$this->actions[$entity->type]['failed']++;
|
||||
Data::increment($entity->via, $entity->type . '_failed');
|
||||
Message::increment("{$entity->via}.{$entity->type}.failed");
|
||||
$this->logger->error(sprintf('MAPPER: %s', $e->getMessage()), [
|
||||
'backend' => $entity->via,
|
||||
'title' => $entity->getName(),
|
||||
@@ -210,13 +210,13 @@ final class DirectMapper implements ImportInterface
|
||||
|
||||
if (null === ($this->changed[$local->id] ?? null)) {
|
||||
$this->actions[$local->type]['updated']++;
|
||||
Data::increment($entity->via, $local->type . '_updated');
|
||||
Message::increment("{$entity->via}.{$local->type}.updated");
|
||||
}
|
||||
|
||||
$this->changed[$local->id] = $this->objects[$local->id] = $local->id;
|
||||
} catch (PDOException $e) {
|
||||
$this->actions[$local->type]['failed']++;
|
||||
Data::increment($entity->via, $local->type . '_failed');
|
||||
Message::increment("{$entity->via}.{$local->type}.failed");
|
||||
$this->logger->error(sprintf('MAPPER: %s', $e->getMessage()), [
|
||||
'id' => $cloned->id,
|
||||
'backend' => $entity->via,
|
||||
@@ -266,13 +266,13 @@ final class DirectMapper implements ImportInterface
|
||||
|
||||
if (null === ($this->changed[$local->id] ?? null)) {
|
||||
$this->actions[$local->type]['updated']++;
|
||||
Data::increment($entity->via, $local->type . '_updated');
|
||||
Message::increment("{$entity->via}.{$local->type}.updated");
|
||||
}
|
||||
|
||||
$this->changed[$local->id] = $this->objects[$local->id] = $local->id;
|
||||
} catch (PDOException $e) {
|
||||
$this->actions[$local->type]['failed']++;
|
||||
Data::increment($entity->via, $local->type . '_failed');
|
||||
Message::increment("{$entity->via}.{$local->type}.failed");
|
||||
$this->logger->error(sprintf('MAPPER: %s', $e->getMessage()), [
|
||||
'id' => $cloned->id,
|
||||
'backend' => $entity->via,
|
||||
@@ -319,13 +319,13 @@ final class DirectMapper implements ImportInterface
|
||||
|
||||
if (null === ($this->changed[$local->id] ?? null)) {
|
||||
$this->actions[$local->type]['updated']++;
|
||||
Data::increment($entity->via, $local->type . '_updated');
|
||||
Message::increment("{$entity->via}.{$local->type}.updated");
|
||||
}
|
||||
|
||||
$this->changed[$local->id] = $this->objects[$local->id] = $local->id;
|
||||
} catch (PDOException $e) {
|
||||
$this->actions[$local->type]['failed']++;
|
||||
Data::increment($entity->via, $local->type . '_failed');
|
||||
Message::increment("{$entity->via}.{$local->type}.failed");
|
||||
$this->logger->error(sprintf('MAPPER: %s', $e->getMessage()), [
|
||||
'id' => $cloned->id,
|
||||
'title' => $cloned->getName(),
|
||||
@@ -340,7 +340,7 @@ final class DirectMapper implements ImportInterface
|
||||
}
|
||||
}
|
||||
|
||||
Data::increment($entity->via, $entity->type . '_ignored_not_played_since_last_sync');
|
||||
Message::increment("{$entity->via}.{$entity->type}.ignored_not_played_since_last_sync");
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -370,13 +370,13 @@ final class DirectMapper implements ImportInterface
|
||||
|
||||
if (null === ($this->changed[$local->id] ?? null)) {
|
||||
$this->actions[$local->type]['updated']++;
|
||||
Data::increment($entity->via, $entity->type . '_updated');
|
||||
Message::increment("{$entity->via}.{$entity->type}.updated");
|
||||
}
|
||||
|
||||
$this->changed[$local->id] = $this->objects[$local->id] = $local->id;
|
||||
} catch (PDOException $e) {
|
||||
$this->actions[$local->type]['failed']++;
|
||||
Data::increment($entity->via, $local->type . '_failed');
|
||||
Message::increment("{$entity->via}.{$local->type}.failed");
|
||||
$this->logger->error(sprintf('MAPPER: %s', $e->getMessage()), [
|
||||
'id' => $cloned->id,
|
||||
'backend' => $entity->via,
|
||||
@@ -403,7 +403,7 @@ final class DirectMapper implements ImportInterface
|
||||
]);
|
||||
}
|
||||
|
||||
Data::increment($entity->via, $entity->type . '_ignored_no_change');
|
||||
Message::increment("{$entity->via}.{$entity->type}.ignored_no_change");
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -507,7 +507,11 @@ final class DirectMapper implements ImportInterface
|
||||
|
||||
protected function addPointers(iFace $entity, string|int $pointer): ImportInterface
|
||||
{
|
||||
foreach ([...$entity->getPointers(), ...$entity->getRelativePointers()] as $key) {
|
||||
foreach ($entity->getRelativePointers() as $key) {
|
||||
$this->pointers[$key] = $pointer;
|
||||
}
|
||||
|
||||
foreach ($entity->getPointers() as $key) {
|
||||
$this->pointers[$key . '/' . $entity->type] = $pointer;
|
||||
}
|
||||
|
||||
@@ -527,18 +531,12 @@ final class DirectMapper implements ImportInterface
|
||||
return $entity->id;
|
||||
}
|
||||
|
||||
// -- Prioritize relative ids for episodes, External ids are often incorrect for episodes.
|
||||
if (true === $entity->isEpisode()) {
|
||||
foreach ($entity->getRelativePointers() as $key) {
|
||||
$lookup = $key . '/' . $entity->type;
|
||||
if (null !== ($this->pointers[$lookup] ?? null)) {
|
||||
return $this->pointers[$lookup];
|
||||
}
|
||||
foreach ($entity->getRelativePointers() as $key) {
|
||||
if (null !== ($this->pointers[$key] ?? null)) {
|
||||
return $this->pointers[$key];
|
||||
}
|
||||
}
|
||||
|
||||
// -- look up movies based on guid.
|
||||
// -- if episode didn't have any match using relative id then fallback to external ids.
|
||||
foreach ($entity->getPointers() as $key) {
|
||||
$lookup = $key . '/' . $entity->type;
|
||||
if (null !== ($this->pointers[$lookup] ?? null)) {
|
||||
@@ -559,12 +557,19 @@ final class DirectMapper implements ImportInterface
|
||||
|
||||
protected function removePointers(iFace $entity): ImportInterface
|
||||
{
|
||||
foreach ([...$entity->getPointers(), ...$entity->getRelativePointers()] as $key) {
|
||||
foreach ($entity->getPointers() as $key) {
|
||||
$lookup = $key . '/' . $entity->type;
|
||||
if (null !== ($this->pointers[$lookup] ?? null)) {
|
||||
unset($this->pointers[$lookup]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($entity->getRelativePointers() as $key) {
|
||||
if (null !== ($this->pointers[$key] ?? null)) {
|
||||
unset($this->pointers[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Libs\Mappers\Import;
|
||||
|
||||
use App\Libs\Data;
|
||||
use App\Libs\Entity\StateInterface as iFace;
|
||||
use App\Libs\Entity\StateInterface as iState;
|
||||
use App\Libs\Guid;
|
||||
use App\Libs\Mappers\ImportInterface;
|
||||
use App\Libs\Message;
|
||||
use App\Libs\Options;
|
||||
use App\Libs\Storage\StorageInterface;
|
||||
use DateTimeInterface;
|
||||
@@ -19,7 +19,7 @@ final class MemoryMapper implements ImportInterface
|
||||
protected const GUID = 'local_db://';
|
||||
|
||||
/**
|
||||
* @var array<int,iFace> Entities table.
|
||||
* @var array<int,iState> Entities table.
|
||||
*/
|
||||
protected array $objects = [];
|
||||
|
||||
@@ -71,7 +71,7 @@ final class MemoryMapper implements ImportInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function add(iFace $entity, array $opts = []): self
|
||||
public function add(iState $entity, array $opts = []): self
|
||||
{
|
||||
if (false === $entity->hasGuids() && false === $entity->hasRelativeGuid()) {
|
||||
$this->logger->warning('MAPPER: Ignoring [%(backend)] [%(title)] no valid/supported external ids.', [
|
||||
@@ -79,7 +79,7 @@ final class MemoryMapper implements ImportInterface
|
||||
'backend' => $entity->via,
|
||||
'title' => $entity->getName(),
|
||||
]);
|
||||
Data::increment($entity->via, $entity->type . '_failed_no_guid');
|
||||
Message::increment("{$entity->via}.{$entity->type}.failed_no_guid");
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ final class MemoryMapper implements ImportInterface
|
||||
*/
|
||||
if (false === ($pointer = $this->getPointer($entity))) {
|
||||
if (true === $metadataOnly) {
|
||||
Data::increment($entity->via, $entity->type . '_failed');
|
||||
Message::increment("{$entity->via}.{$entity->type}.failed");
|
||||
$this->logger->notice('MAPPER: Ignoring [%(backend)] [%(title)]. Does not exist in storage.', [
|
||||
'metaOnly' => true,
|
||||
'backend' => $entity->via,
|
||||
@@ -106,25 +106,25 @@ final class MemoryMapper implements ImportInterface
|
||||
|
||||
$this->changed[$pointer] = $pointer;
|
||||
|
||||
Data::increment($entity->via, $entity->type . '_added');
|
||||
Message::increment("{$entity->via}.{$entity->type}.added");
|
||||
$this->addPointers($this->objects[$pointer], $pointer);
|
||||
|
||||
if (true === $this->inTraceMode()) {
|
||||
$data = $entity->getAll();
|
||||
unset($data['id']);
|
||||
$data[iFace::COLUMN_UPDATED] = makeDate($data[iFace::COLUMN_UPDATED]);
|
||||
$data[iFace::COLUMN_WATCHED] = 0 === $data[iFace::COLUMN_WATCHED] ? 'No' : 'Yes';
|
||||
$data[iState::COLUMN_UPDATED] = makeDate($data[iState::COLUMN_UPDATED]);
|
||||
$data[iState::COLUMN_WATCHED] = 0 === $data[iState::COLUMN_WATCHED] ? 'No' : 'Yes';
|
||||
if ($entity->isMovie()) {
|
||||
unset($data[iFace::COLUMN_SEASON], $data[iFace::COLUMN_EPISODE], $data[iFace::COLUMN_PARENT]);
|
||||
unset($data[iState::COLUMN_SEASON], $data[iState::COLUMN_EPISODE], $data[iState::COLUMN_PARENT]);
|
||||
}
|
||||
} else {
|
||||
$data = [
|
||||
iFace::COLUMN_META_DATA => [
|
||||
iState::COLUMN_META_DATA => [
|
||||
$entity->via => [
|
||||
iFace::COLUMN_ID => ag($entity->getMetadata($entity->via), iFace::COLUMN_ID),
|
||||
iFace::COLUMN_UPDATED => makeDate($entity->updated),
|
||||
iFace::COLUMN_GUIDS => $entity->getGuids(),
|
||||
iFace::COLUMN_PARENT => $entity->getParentGuids(),
|
||||
iState::COLUMN_ID => ag($entity->getMetadata($entity->via), iState::COLUMN_ID),
|
||||
iState::COLUMN_UPDATED => makeDate($entity->updated),
|
||||
iState::COLUMN_GUIDS => $entity->getGuids(),
|
||||
iState::COLUMN_PARENT => $entity->getParentGuids(),
|
||||
]
|
||||
],
|
||||
];
|
||||
@@ -139,7 +139,7 @@ final class MemoryMapper implements ImportInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
$keys = [iFace::COLUMN_META_DATA];
|
||||
$keys = [iState::COLUMN_META_DATA];
|
||||
|
||||
/**
|
||||
* DO NOT operate directly on this object it should be cloned.
|
||||
@@ -152,18 +152,18 @@ final class MemoryMapper implements ImportInterface
|
||||
*/
|
||||
if (true === $metadataOnly) {
|
||||
if (true === (clone $cloned)->apply(entity: $entity, fields: $keys)->isChanged(fields: $keys)) {
|
||||
$localFields = array_merge($keys, [iFace::COLUMN_GUIDS]);
|
||||
$localFields = array_merge($keys, [iState::COLUMN_GUIDS]);
|
||||
$this->changed[$pointer] = $pointer;
|
||||
Data::increment($entity->via, $entity->type . '_updated');
|
||||
Message::increment("{$entity->via}.{$entity->type}.updated");
|
||||
|
||||
$entity->guids = Guid::makeVirtualGuid(
|
||||
$entity->via,
|
||||
ag($entity->getMetadata($entity->via), iFace::COLUMN_ID)
|
||||
ag($entity->getMetadata($entity->via), iState::COLUMN_ID)
|
||||
);
|
||||
|
||||
$this->objects[$pointer] = $this->objects[$pointer]->apply(
|
||||
entity: $entity,
|
||||
fields: array_merge($localFields, [iFace::COLUMN_EXTRA])
|
||||
fields: array_merge($localFields, [iState::COLUMN_EXTRA])
|
||||
);
|
||||
|
||||
$this->removePointers($cloned)->addPointers($this->objects[$pointer], $pointer);
|
||||
@@ -195,21 +195,25 @@ final class MemoryMapper implements ImportInterface
|
||||
// -- Handle mark as unplayed logic.
|
||||
if (false === $entity->isWatched() && true === $cloned->shouldMarkAsUnplayed(backend: $entity)) {
|
||||
$this->changed[$pointer] = $pointer;
|
||||
Data::increment($entity->via, $entity->type . '_updated');
|
||||
Message::increment("{$entity->via}.{$entity->type}.updated");
|
||||
|
||||
$this->objects[$pointer] = $this->objects[$pointer]->apply(
|
||||
entity: $entity,
|
||||
fields: array_merge($keys, [iFace::COLUMN_EXTRA])
|
||||
fields: array_merge($keys, [iState::COLUMN_EXTRA])
|
||||
)->markAsUnplayed(backend: $entity);
|
||||
|
||||
$this->logger->notice('MAPPER: [%(backend)] marked [%(title)] as unplayed.', [
|
||||
'id' => $cloned->id,
|
||||
'backend' => $entity->via,
|
||||
'title' => $cloned->getName(),
|
||||
'changes' => $this->objects[$pointer]->diff(
|
||||
array_merge($keys, [iFace::COLUMN_WATCHED, iFace::COLUMN_UPDATED])
|
||||
),
|
||||
]);
|
||||
$changes = $this->objects[$pointer]->diff(
|
||||
array_merge($keys, [iState::COLUMN_WATCHED, iState::COLUMN_UPDATED])
|
||||
);
|
||||
|
||||
if (count($changes) >= 1) {
|
||||
$this->logger->notice('MAPPER: [%(backend)] marked [%(title)] as unplayed.', [
|
||||
'id' => $cloned->id,
|
||||
'backend' => $entity->via,
|
||||
'title' => $cloned->getName(),
|
||||
'changes' => $changes,
|
||||
]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -220,72 +224,77 @@ final class MemoryMapper implements ImportInterface
|
||||
*/
|
||||
if (true === (bool)ag($this->options, Options::MAPPER_ALWAYS_UPDATE_META)) {
|
||||
if (true === (clone $cloned)->apply(entity: $entity, fields: $keys)->isChanged(fields: $keys)) {
|
||||
$localFields = array_merge($keys, [iFace::COLUMN_GUIDS]);
|
||||
$localFields = array_merge($keys, [iState::COLUMN_GUIDS]);
|
||||
$this->changed[$pointer] = $pointer;
|
||||
Data::increment($entity->via, $entity->type . '_updated');
|
||||
Message::increment("{$entity->via}.{$entity->type}.updated");
|
||||
|
||||
$entity->guids = Guid::makeVirtualGuid(
|
||||
$entity->via,
|
||||
ag($entity->getMetadata($entity->via), iFace::COLUMN_ID)
|
||||
ag($entity->getMetadata($entity->via), iState::COLUMN_ID)
|
||||
);
|
||||
|
||||
$this->objects[$pointer] = $this->objects[$pointer]->apply(
|
||||
entity: $entity,
|
||||
fields: array_merge($localFields, [iFace::COLUMN_EXTRA])
|
||||
fields: array_merge($localFields, [iState::COLUMN_EXTRA])
|
||||
);
|
||||
|
||||
$this->removePointers($cloned)->addPointers($this->objects[$pointer], $pointer);
|
||||
|
||||
$this->logger->notice('MAPPER: [%(backend)] updated [%(title)] metadata.', [
|
||||
'id' => $cloned->id,
|
||||
'backend' => $entity->via,
|
||||
'title' => $cloned->getName(),
|
||||
'changes' => $cloned::fromArray($cloned->getAll())->apply(
|
||||
entity: $entity,
|
||||
fields: $localFields
|
||||
)->diff(fields: $keys),
|
||||
'fields' => implode(',', $localFields),
|
||||
]);
|
||||
$changes = $cloned::fromArray($cloned->getAll())->apply(
|
||||
entity: $entity,
|
||||
fields: $localFields
|
||||
)->diff(fields: $keys);
|
||||
|
||||
if (count($changes) >= 1) {
|
||||
$this->logger->notice('MAPPER: [%(backend)] updated [%(title)] metadata.', [
|
||||
'id' => $cloned->id,
|
||||
'backend' => $entity->via,
|
||||
'title' => $cloned->getName(),
|
||||
'changes' => $changes,
|
||||
'fields' => implode(',', $localFields),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
Data::increment($entity->via, $entity->type . '_ignored_not_played_since_last_sync');
|
||||
Message::increment("{$entity->via}.{$entity->type}.ignored_not_played_since_last_sync");
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
$keys = $opts['diff_keys'] ?? array_flip(
|
||||
array_keys_diff(
|
||||
base: array_flip(iFace::ENTITY_KEYS),
|
||||
list: iFace::ENTITY_IGNORE_DIFF_CHANGES,
|
||||
base: array_flip(iState::ENTITY_KEYS),
|
||||
list: iState::ENTITY_IGNORE_DIFF_CHANGES,
|
||||
has: false
|
||||
)
|
||||
);
|
||||
|
||||
if (true === (clone $cloned)->apply(entity: $entity, fields: $keys)->isChanged(fields: $keys)) {
|
||||
$this->changed[$pointer] = $pointer;
|
||||
Data::increment($entity->via, $entity->type . '_updated');
|
||||
Message::increment("{$entity->via}.{$entity->type}.updated");
|
||||
|
||||
$this->objects[$pointer] = $this->objects[$pointer]->apply(
|
||||
entity: $entity,
|
||||
fields: array_merge($keys, [iFace::COLUMN_EXTRA])
|
||||
fields: array_merge($keys, [iState::COLUMN_EXTRA])
|
||||
);
|
||||
$this->removePointers($cloned)->addPointers($this->objects[$pointer], $pointer);
|
||||
|
||||
$this->logger->notice('MAPPER: [%(backend)] Updated [%(title)].', [
|
||||
'id' => $cloned->id,
|
||||
'backend' => $entity->via,
|
||||
'title' => $cloned->getName(),
|
||||
'changes' => $cloned::fromArray($cloned->getAll())->apply(
|
||||
entity: $entity,
|
||||
fields: $keys
|
||||
)->diff(
|
||||
fields: $keys
|
||||
),
|
||||
'fields' => implode(', ', $keys),
|
||||
]);
|
||||
$changes = $cloned::fromArray($cloned->getAll())->apply(entity: $entity, fields: $keys)->diff(
|
||||
fields: $keys
|
||||
);
|
||||
|
||||
if (count($changes) >= 1) {
|
||||
$this->logger->notice('MAPPER: [%(backend)] Updated [%(title)].', [
|
||||
'id' => $cloned->id,
|
||||
'backend' => $entity->via,
|
||||
'title' => $cloned->getName(),
|
||||
'changes' => $changes,
|
||||
'fields' => implode(', ', $keys),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -302,17 +311,17 @@ final class MemoryMapper implements ImportInterface
|
||||
]);
|
||||
}
|
||||
|
||||
Data::increment($entity->via, $entity->type . '_ignored_no_change');
|
||||
Message::increment("{$entity->via}.{$entity->type}.ignored_no_change");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get(iFace $entity): null|iFace
|
||||
public function get(iState $entity): null|iState
|
||||
{
|
||||
return false === ($pointer = $this->getPointer($entity)) ? null : $this->objects[$pointer];
|
||||
}
|
||||
|
||||
public function remove(iFace $entity): bool
|
||||
public function remove(iState $entity): bool
|
||||
{
|
||||
if (false === ($pointer = $this->getPointer($entity))) {
|
||||
return false;
|
||||
@@ -335,8 +344,8 @@ final class MemoryMapper implements ImportInterface
|
||||
{
|
||||
$state = $this->storage->transactional(function (StorageInterface $storage) {
|
||||
$list = [
|
||||
iFace::TYPE_MOVIE => ['added' => 0, 'updated' => 0, 'failed' => 0],
|
||||
iFace::TYPE_EPISODE => ['added' => 0, 'updated' => 0, 'failed' => 0],
|
||||
iState::TYPE_MOVIE => ['added' => 0, 'updated' => 0, 'failed' => 0],
|
||||
iState::TYPE_EPISODE => ['added' => 0, 'updated' => 0, 'failed' => 0],
|
||||
];
|
||||
|
||||
$count = count($this->changed);
|
||||
@@ -382,7 +391,7 @@ final class MemoryMapper implements ImportInterface
|
||||
return $state;
|
||||
}
|
||||
|
||||
public function has(iFace $entity): bool
|
||||
public function has(iState $entity): bool
|
||||
{
|
||||
return null !== $this->get($entity);
|
||||
}
|
||||
@@ -440,7 +449,7 @@ final class MemoryMapper implements ImportInterface
|
||||
return true === (bool)ag($this->options, Options::DEBUG_TRACE, false);
|
||||
}
|
||||
|
||||
protected function addPointers(iFace $entity, string|int $pointer): ImportInterface
|
||||
protected function addPointers(iState $entity, string|int $pointer): ImportInterface
|
||||
{
|
||||
foreach ($entity->getRelativePointers() as $key) {
|
||||
$this->pointers[$key] = $pointer;
|
||||
@@ -456,24 +465,22 @@ final class MemoryMapper implements ImportInterface
|
||||
/**
|
||||
* Is the object already mapped?
|
||||
*
|
||||
* @param iFace $entity
|
||||
* @param iState $entity
|
||||
*
|
||||
* @return int|string|bool int pointer for the object, Or false if not registered.
|
||||
*/
|
||||
protected function getPointer(iFace $entity): int|string|bool
|
||||
protected function getPointer(iState $entity): int|string|bool
|
||||
{
|
||||
if (null !== $entity->id && null !== ($this->objects[self::GUID . $entity->id] ?? null)) {
|
||||
return self::GUID . $entity->id;
|
||||
}
|
||||
|
||||
// -- Prioritize relative ids for episodes, External ids are often incorrect for episodes.
|
||||
foreach ($entity->getRelativePointers() as $key) {
|
||||
if (null !== ($this->pointers[$key] ?? null)) {
|
||||
return $this->pointers[$key];
|
||||
}
|
||||
}
|
||||
|
||||
// -- fallback to guids for movies and episode in case there was no relative id match.
|
||||
foreach ($entity->getPointers() as $key) {
|
||||
$lookup = $key . '/' . $entity->type;
|
||||
if (null !== ($this->pointers[$lookup] ?? null)) {
|
||||
@@ -492,7 +499,7 @@ final class MemoryMapper implements ImportInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function removePointers(iFace $entity): ImportInterface
|
||||
protected function removePointers(iState $entity): ImportInterface
|
||||
{
|
||||
foreach ($entity->getPointers() as $key) {
|
||||
$lookup = $key . '/' . $entity->type;
|
||||
|
||||
76
src/Libs/Message.php
Normal file
76
src/Libs/Message.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Libs;
|
||||
|
||||
/**
|
||||
* Volatile messaging between classes.
|
||||
* This should not be used for anything important.
|
||||
* Data is mutable, and can be change by anything.
|
||||
* Messages are not persistent and will be removed
|
||||
* once the execution is done.
|
||||
*/
|
||||
final class Message
|
||||
{
|
||||
private static array $data = [];
|
||||
|
||||
/**
|
||||
* Get Message.
|
||||
*
|
||||
* @param string $key message key.
|
||||
* @param mixed|null $default default value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return ag(self::$data, $key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get All Stored Messages.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getAll(): array
|
||||
{
|
||||
return self::$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Message to Store.
|
||||
*
|
||||
* @param string $key Message key.
|
||||
* @param mixed $value value.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function add(string $key, mixed $value): void
|
||||
{
|
||||
self::$data = ag_set(self::$data, $key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* increment key value increment parameter value.
|
||||
*
|
||||
* @param string $key message key.
|
||||
* @param int $increment value. default to 1
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function increment(string $key, int $increment = 1): void
|
||||
{
|
||||
self::$data = ag_set(self::$data, $key, $increment + (int)ag(self::$data, $key, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset Stored data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reset(): void
|
||||
{
|
||||
self::$data = [];
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,15 @@ final class PDOAdapter implements StorageInterface
|
||||
} catch (PDOException $e) {
|
||||
$this->stmt['insert'] = null;
|
||||
if (false === $this->viaTransaction && false === $this->singleTransaction) {
|
||||
$this->logger->error($e->getMessage(), $entity->getAll());
|
||||
$this->logger->error($e->getMessage(), [
|
||||
'entity' => $entity->getAll(),
|
||||
'exception' => [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'message' => $e->getMessage(),
|
||||
'trace' => $e->getTrace(),
|
||||
],
|
||||
]);
|
||||
return $entity;
|
||||
}
|
||||
throw $e;
|
||||
@@ -241,7 +249,15 @@ final class PDOAdapter implements StorageInterface
|
||||
} catch (PDOException $e) {
|
||||
$this->stmt['update'] = null;
|
||||
if (false === $this->viaTransaction && false === $this->singleTransaction) {
|
||||
$this->logger->error($e->getMessage(), $entity->getAll());
|
||||
$this->logger->error($e->getMessage(), [
|
||||
'entity' => $entity->getAll(),
|
||||
'exception' => [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'message' => $e->getMessage(),
|
||||
'trace' => $e->getTrace(),
|
||||
]
|
||||
]);
|
||||
return $entity;
|
||||
}
|
||||
throw $e;
|
||||
@@ -268,7 +284,15 @@ final class PDOAdapter implements StorageInterface
|
||||
|
||||
$this->query(sprintf('DELETE FROM state WHERE %s = %d', iFace::COLUMN_ID, (int)$id));
|
||||
} catch (PDOException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
$this->logger->error($e->getMessage(), [
|
||||
'entity' => $entity->getAll(),
|
||||
'exception' => [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'message' => $e->getMessage(),
|
||||
'trace' => $e->getTrace(),
|
||||
],
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -295,7 +319,15 @@ final class PDOAdapter implements StorageInterface
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$actions['failed']++;
|
||||
$this->logger->error($e->getMessage(), $entity->getAll());
|
||||
$this->logger->error($e->getMessage(), [
|
||||
'entity' => $entity->getAll(),
|
||||
'exception' => [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'message' => $e->getMessage(),
|
||||
'trace' => $e->getTrace(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,7 +551,7 @@ final class PDOAdapter implements StorageInterface
|
||||
try {
|
||||
return $stmt->execute($cond);
|
||||
} catch (PDOException $e) {
|
||||
if (false !== stripos($e->getMessage(), 'database is locked')) {
|
||||
if (true === str_contains(strtolower($e->getMessage()), 'database is locked')) {
|
||||
if ($i >= self::LOCK_RETRY) {
|
||||
throw $e;
|
||||
}
|
||||
@@ -545,7 +577,7 @@ final class PDOAdapter implements StorageInterface
|
||||
try {
|
||||
return $this->pdo->query($sql);
|
||||
} catch (PDOException $e) {
|
||||
if (false !== stripos($e->getMessage(), 'database is locked')) {
|
||||
if (true === str_contains(strtolower($e->getMessage()), 'database is locked')) {
|
||||
if ($i >= self::LOCK_RETRY) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use Nyholm\Psr7\Response;
|
||||
use Nyholm\Psr7\Uri;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
@@ -567,16 +568,106 @@ if (false === function_exists('getPeakMemoryUsage')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (false === function_exists('isIgnoredId')) {
|
||||
function isIgnoredId(string $backend, string $type, string $db, string|int $id): bool
|
||||
if (false === function_exists('makeIgnoreId')) {
|
||||
function makeIgnoreId(string $url): UriInterface
|
||||
{
|
||||
static $filterQuery = null;
|
||||
|
||||
if (null === $filterQuery) {
|
||||
$filterQuery = function (string $query): string {
|
||||
$list = $final = [];
|
||||
$allowed = ['id'];
|
||||
|
||||
parse_str($query, $list);
|
||||
|
||||
foreach ($list as $key => $val) {
|
||||
if (false === in_array($key, $allowed) || empty($val)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$final[$key] = $val;
|
||||
}
|
||||
|
||||
return http_build_query($final);
|
||||
};
|
||||
}
|
||||
|
||||
$id = (new Uri($url))->withPath('')->withFragment('')->withPort(null);
|
||||
return $id->withQuery($filterQuery($id->getQuery()));
|
||||
}
|
||||
}
|
||||
|
||||
if (false === function_exists('isIgnoredId')) {
|
||||
function isIgnoredId(
|
||||
string $backend,
|
||||
string $type,
|
||||
string $db,
|
||||
string|int $id,
|
||||
string|int|null $backendId = null
|
||||
): bool {
|
||||
if (false === in_array($type, iFace::TYPES_LIST)) {
|
||||
throw new RuntimeException(sprintf('Invalid context type \'%s\' was given.', $type));
|
||||
}
|
||||
|
||||
return ag_exists(
|
||||
Config::get('ignore', []),
|
||||
sprintf('%s://%s:%s@%s', $type, $db, $id, $backend)
|
||||
);
|
||||
$list = Config::get('ignore', []);
|
||||
|
||||
$key = makeIgnoreId(sprintf('%s://%s:%s@%s?id=%s', $type, $db, $id, $backend, $backendId));
|
||||
|
||||
if (null !== ($list[(string)$key->withQuery('')] ?? null)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (null === $backendId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return null !== ($list[(string)$key] ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
if (false === function_exists('replacer')) {
|
||||
function replacer(string $text, array $context = []): string
|
||||
{
|
||||
if (false === str_contains($text, '{') || false === str_contains($text, '}')) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$pattern = '#' . preg_quote('{', '#') . '([\w\d_.]+)' . preg_quote('}', '#') . '#is';
|
||||
|
||||
$status = preg_match_all($pattern, $text, $matches);
|
||||
|
||||
if (false === $status || $status < 1) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$replacements = [];
|
||||
|
||||
foreach ($matches[1] as $key) {
|
||||
$placeholder = '{' . $key . '}';
|
||||
|
||||
if (false === str_contains($text, $placeholder)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (false === ag_exists($context, $key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$val = ag($context, $key);
|
||||
|
||||
$context = ag_delete($context, $key);
|
||||
|
||||
if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, '__toString'))) {
|
||||
$replacements[$placeholder] = $val;
|
||||
} elseif (is_object($val)) {
|
||||
$replacements[$placeholder] = implode(',', get_object_vars($val));
|
||||
} elseif (is_array($val)) {
|
||||
$replacements[$placeholder] = implode(',', $val);
|
||||
} else {
|
||||
$replacements[$placeholder] = '[' . gettype($val) . ']';
|
||||
}
|
||||
}
|
||||
|
||||
return strtr($text, $replacements);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Mappers\Import;
|
||||
|
||||
use App\Libs\Data;
|
||||
use App\Libs\Entity\StateEntity;
|
||||
use App\Libs\Entity\StateInterface as iFace;
|
||||
use App\Libs\Guid;
|
||||
use App\Libs\Mappers\Import\DirectMapper;
|
||||
use App\Libs\Message;
|
||||
use App\Libs\Storage\PDO\PDOAdapter;
|
||||
use App\Libs\Storage\StorageInterface;
|
||||
use Monolog\Handler\TestHandler;
|
||||
@@ -47,7 +47,7 @@ class DirectMapperTest extends TestCase
|
||||
$this->mapper = new DirectMapper($logger, $this->storage);
|
||||
$this->mapper->setOptions(options: ['class' => new StateEntity([])]);
|
||||
|
||||
Data::reset();
|
||||
Message::reset();
|
||||
}
|
||||
|
||||
public function test_add_conditions(): void
|
||||
|
||||
@@ -4,11 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Mappers\Import;
|
||||
|
||||
use App\Libs\Data;
|
||||
use App\Libs\Entity\StateEntity;
|
||||
use App\Libs\Entity\StateInterface as iFace;
|
||||
use App\Libs\Entity\StateInterface as iState;
|
||||
use App\Libs\Guid;
|
||||
use App\Libs\Mappers\Import\MemoryMapper;
|
||||
use App\Libs\Message;
|
||||
use App\Libs\Storage\PDO\PDOAdapter;
|
||||
use App\Libs\Storage\StorageInterface;
|
||||
use Monolog\Handler\TestHandler;
|
||||
@@ -46,7 +46,7 @@ class MemoryMapperTest extends TestCase
|
||||
$this->mapper = new MemoryMapper($logger, $this->storage);
|
||||
$this->mapper->setOptions(options: ['class' => new StateEntity([])]);
|
||||
|
||||
Data::reset();
|
||||
Message::reset();
|
||||
}
|
||||
|
||||
public function test_loadData_null_date_conditions(): void
|
||||
@@ -68,7 +68,7 @@ class MemoryMapperTest extends TestCase
|
||||
{
|
||||
$time = time();
|
||||
|
||||
$this->testEpisode[iFace::COLUMN_UPDATED] = $time;
|
||||
$this->testEpisode[iState::COLUMN_UPDATED] = $time;
|
||||
|
||||
$testMovie = new StateEntity($this->testMovie);
|
||||
$testEpisode = new StateEntity($this->testEpisode);
|
||||
@@ -97,8 +97,8 @@ class MemoryMapperTest extends TestCase
|
||||
|
||||
$this->assertSame(
|
||||
[
|
||||
iFace::TYPE_MOVIE => ['added' => 1, 'updated' => 0, 'failed' => 0],
|
||||
iFace::TYPE_EPISODE => ['added' => 1, 'updated' => 0, 'failed' => 0],
|
||||
iState::TYPE_MOVIE => ['added' => 1, 'updated' => 0, 'failed' => 0],
|
||||
iState::TYPE_EPISODE => ['added' => 1, 'updated' => 0, 'failed' => 0],
|
||||
],
|
||||
$this->mapper->commit()
|
||||
);
|
||||
@@ -106,7 +106,7 @@ class MemoryMapperTest extends TestCase
|
||||
// -- assert 0 as we have committed the changes to the db, and the state should have been reset.
|
||||
$this->assertCount(0, $this->mapper);
|
||||
|
||||
$testEpisode->metadata['home_plex'][iFace::COLUMN_GUIDS][Guid::GUID_TVRAGE] = '2';
|
||||
$testEpisode->metadata['home_plex'][iState::COLUMN_GUIDS][Guid::GUID_TVRAGE] = '2';
|
||||
|
||||
$this->mapper->add($testEpisode);
|
||||
|
||||
@@ -114,8 +114,8 @@ class MemoryMapperTest extends TestCase
|
||||
|
||||
$this->assertSame(
|
||||
[
|
||||
iFace::TYPE_MOVIE => ['added' => 0, 'updated' => 0, 'failed' => 0],
|
||||
iFace::TYPE_EPISODE => ['added' => 0, 'updated' => 1, 'failed' => 0],
|
||||
iState::TYPE_MOVIE => ['added' => 0, 'updated' => 0, 'failed' => 0],
|
||||
iState::TYPE_EPISODE => ['added' => 0, 'updated' => 1, 'failed' => 0],
|
||||
],
|
||||
$this->mapper->commit()
|
||||
);
|
||||
@@ -128,7 +128,7 @@ class MemoryMapperTest extends TestCase
|
||||
$movie = $this->testMovie;
|
||||
$episode = $this->testEpisode;
|
||||
|
||||
foreach (iFace::ENTITY_ARRAY_KEYS as $key) {
|
||||
foreach (iState::ENTITY_ARRAY_KEYS as $key) {
|
||||
if (null !== ($movie[$key] ?? null)) {
|
||||
ksort($movie[$key]);
|
||||
}
|
||||
@@ -170,7 +170,7 @@ class MemoryMapperTest extends TestCase
|
||||
$this->assertNull($this->mapper->get($testEpisode));
|
||||
|
||||
$this->mapper->loadData(makeDate($time - 1));
|
||||
$this->assertInstanceOf(iFace::class, $this->mapper->get($testEpisode));
|
||||
$this->assertInstanceOf(iState::class, $this->mapper->get($testEpisode));
|
||||
}
|
||||
|
||||
public function test_commit_conditions(): void
|
||||
@@ -185,25 +185,25 @@ class MemoryMapperTest extends TestCase
|
||||
|
||||
$this->assertSame(
|
||||
[
|
||||
iFace::TYPE_MOVIE => ['added' => 1, 'updated' => 0, 'failed' => 0],
|
||||
iFace::TYPE_EPISODE => ['added' => 1, 'updated' => 0, 'failed' => 0],
|
||||
iState::TYPE_MOVIE => ['added' => 1, 'updated' => 0, 'failed' => 0],
|
||||
iState::TYPE_EPISODE => ['added' => 1, 'updated' => 0, 'failed' => 0],
|
||||
],
|
||||
$insert
|
||||
);
|
||||
|
||||
$testMovie->metadata['home_plex'][iFace::COLUMN_GUIDS][Guid::GUID_ANIDB] = '1920';
|
||||
$testEpisode->metadata['home_plex'][iFace::COLUMN_GUIDS][Guid::GUID_ANIDB] = '1900';
|
||||
$testMovie->metadata['home_plex'][iState::COLUMN_GUIDS][Guid::GUID_ANIDB] = '1920';
|
||||
$testEpisode->metadata['home_plex'][iState::COLUMN_GUIDS][Guid::GUID_ANIDB] = '1900';
|
||||
|
||||
$this->mapper
|
||||
->add($testMovie, ['diff_keys' => iFace::ENTITY_KEYS])
|
||||
->add($testEpisode, ['diff_keys' => iFace::ENTITY_KEYS]);
|
||||
->add($testMovie, ['diff_keys' => iState::ENTITY_KEYS])
|
||||
->add($testEpisode, ['diff_keys' => iState::ENTITY_KEYS]);
|
||||
|
||||
$updated = $this->mapper->commit();
|
||||
|
||||
$this->assertSame(
|
||||
[
|
||||
iFace::TYPE_MOVIE => ['added' => 0, 'updated' => 1, 'failed' => 0],
|
||||
iFace::TYPE_EPISODE => ['added' => 0, 'updated' => 1, 'failed' => 0],
|
||||
iState::TYPE_MOVIE => ['added' => 0, 'updated' => 1, 'failed' => 0],
|
||||
iState::TYPE_EPISODE => ['added' => 0, 'updated' => 1, 'failed' => 0],
|
||||
],
|
||||
$updated
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user