From 775405e9bbff44560f4c32d7d7dfa6ba23b5aa83 Mon Sep 17 00:00:00 2001 From: "Abdulmhsen B. A. A" Date: Wed, 6 Jul 2022 19:24:13 +0300 Subject: [PATCH] Updated mappers. --- src/Libs/Mappers/Import/DirectMapper.php | 169 ++++++++++++++-------- src/Libs/Mappers/Import/MemoryMapper.php | 60 ++++---- src/Libs/Mappers/Import/RestoreMapper.php | 14 +- src/Libs/Mappers/ImportInterface.php | 14 ++ 4 files changed, 164 insertions(+), 93 deletions(-) diff --git a/src/Libs/Mappers/Import/DirectMapper.php b/src/Libs/Mappers/Import/DirectMapper.php index 755b00ba..17305840 100644 --- a/src/Libs/Mappers/Import/DirectMapper.php +++ b/src/Libs/Mappers/Import/DirectMapper.php @@ -5,18 +5,18 @@ declare(strict_types=1); namespace App\Libs\Mappers\Import; use App\Libs\Container; -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 App\Libs\Storage\StorageInterface; -use DateTimeInterface; +use App\Libs\Storage\StorageInterface as iStorage; +use DateTimeInterface as iDate; use Exception; use PDOException; -use Psr\Log\LoggerInterface; +use Psr\Log\LoggerInterface as iLogger; -final class DirectMapper implements ImportInterface +final class DirectMapper implements iImport { /** * @var array List used objects. @@ -37,36 +37,36 @@ final class DirectMapper implements ImportInterface * @var array> */ protected array $actions = [ - 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], ]; protected array $options = []; protected bool $fullyLoaded = false; - public function __construct(protected LoggerInterface $logger, protected StorageInterface $storage) + public function __construct(protected iLogger $logger, protected iStorage $storage) { } - public function setOptions(array $options = []): ImportInterface + public function setOptions(array $options = []): iImport { $this->options = $options; return $this; } - public function loadData(DateTimeInterface|null $date = null): self + public function loadData(iDate|null $date = null): self { $this->fullyLoaded = null === $date; $opts = [ 'class' => $this->options['class'] ?? null, 'fields' => [ - iFace::COLUMN_ID, - iFace::COLUMN_TYPE, - iFace::COLUMN_PARENT, - iFace::COLUMN_GUIDS, + iState::COLUMN_ID, + iState::COLUMN_TYPE, + iState::COLUMN_PARENT, + iState::COLUMN_GUIDS, ], ]; @@ -89,7 +89,7 @@ final class DirectMapper implements ImportInterface return $this; } - public function add(iFace $entity, array $opts = []): self + public function add(iState $entity, array $opts = []): self { if (!$entity->hasGuids() && !$entity->hasRelativeGuid()) { $this->logger->warning('MAPPER: Ignoring [%(backend)] [%(title)] no valid/supported external ids.', [ @@ -126,19 +126,19 @@ final class DirectMapper implements ImportInterface if ($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(), ] ], ]; @@ -178,8 +178,13 @@ final class DirectMapper implements ImportInterface return $this; } + $keys = [iState::COLUMN_META_DATA]; + + /** + * DO NOT operate directly on this object it should be cloned. + * It should maintain pristine condition until changes are committed. + */ $cloned = clone $local; - $keys = [iFace::COLUMN_META_DATA]; /** * Only allow metadata updates no play state changes. @@ -187,14 +192,15 @@ final class DirectMapper implements ImportInterface if (true === $metadataOnly) { if (true === (clone $cloned)->apply(entity: $entity, fields: $keys)->isChanged(fields: $keys)) { try { - $localFields = array_merge($keys, [iFace::COLUMN_GUIDS]); + $localFields = array_merge($keys, [iState::COLUMN_GUIDS]); $entity->guids = Guid::makeVirtualGuid( $entity->via, - ag($entity->getMetadata($entity->via), iFace::COLUMN_ID) + ag($entity->getMetadata($entity->via), iState::COLUMN_ID) ); - $local = $local->apply(entity: $entity, fields: array_merge($localFields, [iFace::COLUMN_EXTRA])); + $local = $local->apply(entity: $entity, fields: array_merge($localFields, [iState::COLUMN_EXTRA])); + $this->removePointers($cloned)->addPointers($local, $local->id); $this->logger->notice('MAPPER: [%(backend)] updated [%(title)] metadata.', [ @@ -242,15 +248,15 @@ final class DirectMapper implements ImportInterface return $this; } - // -- Item date is older than recorded last sync date, - if (null !== ($opts['after'] ?? null) && true === ($opts['after'] instanceof DateTimeInterface)) { + // -- Item date is older than recorded last sync date logic handling. + if (null !== ($opts['after'] ?? null) && true === ($opts['after'] instanceof iDate)) { if ($opts['after']->getTimestamp() >= $entity->updated) { // -- Handle mark as unplayed logic. if (false === $entity->isWatched() && true === $cloned->shouldMarkAsUnplayed(backend: $entity)) { try { $local = $local->apply( entity: $entity, - fields: array_merge($keys, [iFace::COLUMN_EXTRA]) + fields: array_merge($keys, [iState::COLUMN_EXTRA]) )->markAsUnplayed($entity); if (false === $inDryRunMode) { @@ -294,24 +300,30 @@ final class DirectMapper 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)) { try { - $localFields = array_merge($keys, [iFace::COLUMN_GUIDS]); + $localFields = array_merge($keys, [iState::COLUMN_GUIDS]); $entity->guids = Guid::makeVirtualGuid( $entity->via, - ag($entity->getMetadata($entity->via), 'id') + ag($entity->getMetadata($entity->via), iState::COLUMN_ID) ); $local = $local->apply( entity: $entity, - fields: array_merge($localFields, [iFace::COLUMN_EXTRA]) + fields: array_merge($localFields, [iState::COLUMN_EXTRA]) ); - $this->logger->notice('MAPPER: [%(backend)] updated [%(title)] metadata.', [ - 'id' => $cloned->id, - 'backend' => $entity->via, - 'title' => $cloned->getName(), - 'changes' => $local->diff(fields: $localFields), - ]); + $this->removePointers($cloned)->addPointers($local, $local->id); + + $changes = $local->diff(fields: $localFields); + + if (count($changes) >= 1) { + $this->logger->notice('MAPPER: [%(backend)] updated [%(title)] metadata.', [ + 'id' => $cloned->id, + 'backend' => $entity->via, + 'title' => $cloned->getName(), + 'changes' => $local->diff(fields: $localFields), + ]); + } if (false === $inDryRunMode) { $this->storage->update($local); @@ -347,22 +359,31 @@ final class DirectMapper implements ImportInterface $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)) { try { - $local = $local->apply(entity: $entity, fields: array_merge($keys, [iFace::COLUMN_EXTRA])); + $local = $local->apply( + entity: $entity, + fields: array_merge($keys, [iState::COLUMN_EXTRA]) + ); - $this->logger->notice('MAPPER: [%(backend)] Updated [%(title)].', [ - 'id' => $cloned->id, - 'backend' => $entity->via, - 'title' => $cloned->getName(), - 'changes' => $local->diff(fields: $keys) - ]); + $this->removePointers($cloned)->addPointers($local, $local->id); + + $changes = $local->diff(fields: $keys); + + if (count($changes) >= 1) { + $this->logger->notice('MAPPER: [%(backend)] Updated [%(title)].', [ + 'id' => $cloned->id, + 'backend' => $entity->via, + 'title' => $cloned->getName(), + 'changes' => $local->diff(fields: $keys) + ]); + } if (false === $inDryRunMode) { $this->storage->update($local); @@ -408,13 +429,13 @@ final class DirectMapper implements ImportInterface return $this; } - public function get(iFace $entity): null|iFace + public function get(iState $entity): null|iState { if (false === ($pointer = $this->getPointer($entity))) { return null; } - if (true === ($pointer instanceof iFace)) { + if (true === ($pointer instanceof iState)) { return $pointer; } @@ -423,8 +444,18 @@ final class DirectMapper implements ImportInterface return $this->storage->get($entity); } - public function remove(iFace $entity): bool + public function remove(iState $entity): bool { + $this->removePointers($entity); + + if (null !== ($this->objects[$entity->id] ?? null)) { + unset($this->objects[$entity->id]); + } + + if (null !== ($this->changed[$entity->id] ?? null)) { + unset($this->changed[$entity->id]); + } + return $this->storage->remove($entity); } @@ -437,7 +468,7 @@ final class DirectMapper implements ImportInterface return $list; } - public function has(iFace $entity): bool + public function has(iState $entity): bool { return null !== $this->get($entity); } @@ -445,8 +476,8 @@ final class DirectMapper implements ImportInterface public function reset(): self { $this->actions = [ - 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], ]; $this->fullyLoaded = false; @@ -459,10 +490,10 @@ final class DirectMapper implements ImportInterface { $list = []; - $entity = $this->options['class'] ?? Container::get(iFace::class); + $entity = $this->options['class'] ?? Container::get(iState::class); foreach ($this->objects as $id) { - $list[] = $entity::fromArray([iFace::COLUMN_ID => $id]); + $list[] = $entity::fromArray([iState::COLUMN_ID => $id]); } if (empty($list)) { @@ -482,14 +513,14 @@ final class DirectMapper implements ImportInterface return count($this->changed); } - public function setLogger(LoggerInterface $logger): self + public function setLogger(iLogger $logger): self { $this->logger = $logger; $this->storage->setLogger($logger); return $this; } - public function setStorage(StorageInterface $storage): self + public function setStorage(iStorage $storage): self { $this->storage = $storage; return $this; @@ -505,7 +536,17 @@ final class DirectMapper implements ImportInterface return true === (bool)ag($this->options, Options::DEBUG_TRACE, false); } - protected function addPointers(iFace $entity, string|int $pointer): ImportInterface + public function getPointersList(): array + { + return $this->pointers; + } + + public function getChangedList(): array + { + return $this->changed; + } + + protected function addPointers(iState $entity, string|int $pointer): iImport { foreach ($entity->getRelativePointers() as $key) { $this->pointers[$key] = $pointer; @@ -521,11 +562,11 @@ final class DirectMapper implements ImportInterface /** * Is the object already mapped? * - * @param iFace $entity + * @param iState $entity * - * @return iFace|int|string|bool int pointer for the object, Or false if not registered. + * @return iState|int|string|bool int|string pointer for the object, or false if not registered. */ - protected function getPointer(iFace $entity): iFace|int|string|bool + protected function getPointer(iState $entity): iState|int|string|bool { if (null !== $entity->id && null !== ($this->objects[$entity->id] ?? null)) { return $entity->id; @@ -555,7 +596,7 @@ final class DirectMapper implements ImportInterface return false; } - protected function removePointers(iFace $entity): ImportInterface + protected function removePointers(iState $entity): iImport { foreach ($entity->getPointers() as $key) { $lookup = $key . '/' . $entity->type; diff --git a/src/Libs/Mappers/Import/MemoryMapper.php b/src/Libs/Mappers/Import/MemoryMapper.php index 038767d6..e7bb91ce 100644 --- a/src/Libs/Mappers/Import/MemoryMapper.php +++ b/src/Libs/Mappers/Import/MemoryMapper.php @@ -6,15 +6,15 @@ namespace App\Libs\Mappers\Import; 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 App\Libs\Storage\StorageInterface; -use DateTimeInterface; +use App\Libs\Storage\StorageInterface as iStorage; +use DateTimeInterface as iDate; use PDOException; -use Psr\Log\LoggerInterface; +use Psr\Log\LoggerInterface as iLogger; -final class MemoryMapper implements ImportInterface +final class MemoryMapper implements iImport { protected const GUID = 'local_db://'; @@ -37,18 +37,18 @@ final class MemoryMapper implements ImportInterface protected bool $fullyLoaded = false; - public function __construct(protected LoggerInterface $logger, protected StorageInterface $storage) + public function __construct(protected iLogger $logger, protected iStorage $storage) { } - public function setOptions(array $options = []): ImportInterface + public function setOptions(array $options = []): iImport { $this->options = $options; return $this; } - public function loadData(DateTimeInterface|null $date = null): self + public function loadData(iDate|null $date = null): self { $this->fullyLoaded = null === $date; @@ -87,7 +87,6 @@ final class MemoryMapper implements ImportInterface /** * Handle new item logic here. - * if getPointer return false, it means most likely the item is not found in storage. */ if (false === ($pointer = $this->getPointer($entity))) { if (true === $metadataOnly) { @@ -190,7 +189,7 @@ final class MemoryMapper implements ImportInterface } // -- Item date is older than recorded last sync date logic handling. - if (null !== ($opts['after'] ?? null) && true === ($opts['after'] instanceof DateTimeInterface)) { + if (null !== ($opts['after'] ?? null) && true === ($opts['after'] instanceof iDate)) { if ($opts['after']->getTimestamp() >= $entity->updated) { // -- Handle mark as unplayed logic. if (false === $entity->isWatched() && true === $cloned->shouldMarkAsUnplayed(backend: $entity)) { @@ -240,10 +239,7 @@ final class MemoryMapper implements ImportInterface $this->removePointers($cloned)->addPointers($this->objects[$pointer], $pointer); - $changes = $cloned::fromArray($cloned->getAll())->apply( - entity: $entity, - fields: $localFields - )->diff(fields: $keys); + $changes = $this->objects[$pointer]->diff(fields: $keys); if (count($changes) >= 1) { $this->logger->notice('MAPPER: [%(backend)] updated [%(title)] metadata.', [ @@ -280,11 +276,10 @@ final class MemoryMapper implements ImportInterface entity: $entity, fields: array_merge($keys, [iState::COLUMN_EXTRA]) ); + $this->removePointers($cloned)->addPointers($this->objects[$pointer], $pointer); - $changes = $cloned::fromArray($cloned->getAll())->apply(entity: $entity, fields: $keys)->diff( - fields: $keys - ); + $changes = $this->objects[$pointer]->diff(fields: $keys); if (count($changes) >= 1) { $this->logger->notice('MAPPER: [%(backend)] Updated [%(title)].', [ @@ -327,11 +322,13 @@ final class MemoryMapper implements ImportInterface return false; } - $this->storage->remove($this->objects[$pointer]); - $this->removePointers($this->objects[$pointer]); - unset($this->objects[$pointer]); + $this->storage->remove($this->objects[$pointer]); + + if (null !== ($this->objects[$pointer] ?? null)) { + unset($this->objects[$pointer]); + } if (null !== ($this->changed[$pointer] ?? null)) { unset($this->changed[$pointer]); @@ -342,7 +339,7 @@ final class MemoryMapper implements ImportInterface public function commit(): mixed { - $state = $this->storage->transactional(function (StorageInterface $storage) { + $state = $this->storage->transactional(function (iStorage $storage) { $list = [ iState::TYPE_MOVIE => ['added' => 0, 'updated' => 0, 'failed' => 0], iState::TYPE_EPISODE => ['added' => 0, 'updated' => 0, 'failed' => 0], @@ -419,14 +416,14 @@ final class MemoryMapper implements ImportInterface return count($this->changed); } - public function setLogger(LoggerInterface $logger): self + public function setLogger(iLogger $logger): self { $this->logger = $logger; $this->storage->setLogger($logger); return $this; } - public function setStorage(StorageInterface $storage): self + public function setStorage(iStorage $storage): self { $this->storage = $storage; return $this; @@ -449,7 +446,17 @@ final class MemoryMapper implements ImportInterface return true === (bool)ag($this->options, Options::DEBUG_TRACE, false); } - protected function addPointers(iState $entity, string|int $pointer): ImportInterface + public function getPointersList(): array + { + return $this->pointers; + } + + public function getChangedList(): array + { + return $this->changed; + } + + protected function addPointers(iState $entity, string|int $pointer): iImport { foreach ($entity->getRelativePointers() as $key) { $this->pointers[$key] = $pointer; @@ -467,7 +474,7 @@ final class MemoryMapper implements ImportInterface * * @param iState $entity * - * @return int|string|bool int pointer for the object, Or false if not registered. + * @return int|string|bool int|string pointer for the object, or false if not registered. */ protected function getPointer(iState $entity): int|string|bool { @@ -499,7 +506,7 @@ final class MemoryMapper implements ImportInterface return false; } - protected function removePointers(iState $entity): ImportInterface + protected function removePointers(iState $entity): iImport { foreach ($entity->getPointers() as $key) { $lookup = $key . '/' . $entity->type; @@ -516,5 +523,4 @@ final class MemoryMapper implements ImportInterface return $this; } - } diff --git a/src/Libs/Mappers/Import/RestoreMapper.php b/src/Libs/Mappers/Import/RestoreMapper.php index 5c9642be..b025c5bb 100644 --- a/src/Libs/Mappers/Import/RestoreMapper.php +++ b/src/Libs/Mappers/Import/RestoreMapper.php @@ -9,7 +9,7 @@ use App\Libs\Entity\StateInterface as iState; use App\Libs\Guid; use App\Libs\Mappers\ImportInterface as iImport; use App\Libs\Storage\StorageInterface as iStorage; -use DateTimeInterface; +use DateTimeInterface as iDate; use JsonMachine\Items; use JsonMachine\JsonDecoder\DecodingError; use JsonMachine\JsonDecoder\ErrorWrappingDecoder; @@ -44,7 +44,7 @@ final class RestoreMapper implements iImport /** * @throws \JsonMachine\Exception\InvalidArgumentException */ - public function loadData(DateTimeInterface|null $date = null): self + public function loadData(iDate|null $date = null): self { $it = Items::fromFile($this->file, [ 'decoder' => new ErrorWrappingDecoder(new ExtJsonDecoder(true, JSON_INVALID_UTF8_IGNORE)) @@ -186,4 +186,14 @@ final class RestoreMapper implements iImport return false; } + + public function getPointersList(): array + { + return $this->pointers; + } + + public function getChangedList(): array + { + return []; + } } diff --git a/src/Libs/Mappers/ImportInterface.php b/src/Libs/Mappers/ImportInterface.php index 29697ccb..661e4d56 100644 --- a/src/Libs/Mappers/ImportInterface.php +++ b/src/Libs/Mappers/ImportInterface.php @@ -128,4 +128,18 @@ interface ImportInterface extends Countable * @return bool */ public function inTraceMode(): bool; + + /** + * Get List Of registered pointers. + * + * @return array + */ + public function getPointersList(): array; + + /** + * Get List of changed items that changed. + * + * @return array + */ + public function getChangedList(): array; }