Added StateEntity::getMeta method to get metadata content based on quorum

This commit is contained in:
abdulmohsen
2024-07-12 14:24:46 +03:00
parent 5f8b85bf8f
commit c43c79940d
5 changed files with 158 additions and 5 deletions

View File

@@ -654,6 +654,46 @@ final class StateEntity implements iState
return ag($this->context, $key, $default);
}
/**
* @inheritdoc
*/
public function getMeta(string $key, mixed $default = null): mixed
{
if (empty($this->via)) {
$this->logger?->warning('StateEntity: No backend was set in $this->via parameter.');
return $default;
}
$values = [];
$total = count($this->metadata);
$quorum = round($total / 2, 0, PHP_ROUND_HALF_UP);
if ($quorum < 2) {
$this->logger?->warning("StateEntity: Quorum is less than 2. '{quorum}' Using default value.", [
'quorum' => $quorum
]);
return ag($this->metadata[$this->via], $key, $default);
}
foreach ($this->metadata as $data) {
if (null === ($value = ag($data, $key, null))) {
continue;
}
$values[$value] = isset($values[$value]) ? $values[$value] + 1 : 1;
}
foreach ($values as $value => $count) {
if ($count >= $quorum) {
$this->logger?->info('StateEntity: quorum found. Using value from {value}.', ['value' => $value]);
return $value;
}
}
$this->logger?->warning('StateEntity: no quorum found. Using default value.');
return ag($this->metadata[$this->via], $key, $default);
}
/**
* @inheritdoc
*/

View File

@@ -402,6 +402,18 @@ interface StateInterface extends LoggerAwareInterface
*/
public function getContext(string|null $key = null, mixed $default = null): mixed;
/**
* Get the metadata that is likely to be correct based on the quorum.
* To constitute a quorum, 2/3 of the backends must have the same metadata, otherwise fallback to
* {@see ag($this->getMetadata($this->via), $key, $default)}
*
* @param string $key key
* @param mixed|null $default default value.
*
* @return mixed
*/
public function getMeta(string $key, mixed $default = null): mixed;
/**
* Check if entity has contextual data.
*

View File

@@ -192,11 +192,7 @@ trait APITraits
$item[iState::COLUMN_META_DATA_PROGRESS] = $entity->hasPlayProgress() ? $entity->getPlayProgress() : null;
$item[iState::COLUMN_EXTRA_EVENT] = ag($entity->getExtra($entity->via), iState::COLUMN_EXTRA_EVENT, null);
$item['content_title'] = $entity->isEpisode() ? ag(
$entity->getMetadata($entity->via),
iState::COLUMN_EXTRA . '.' . iState::COLUMN_TITLE,
null
) : null;
$item['content_title'] = $entity->getMeta(iState::COLUMN_EXTRA . '.' . iState::COLUMN_TITLE, null);
$item['content_path'] = ag($entity->getMetadata($entity->via), iState::COLUMN_META_PATH);
$item['rguids'] = [];

View File

@@ -43,12 +43,50 @@ return [
iState::COLUMN_META_DATA_ADDED_AT => 1,
iState::COLUMN_META_DATA_PLAYED_AT => 2,
],
'home_jellyfin' => [
iState::COLUMN_ID => 122,
iState::COLUMN_TYPE => iState::TYPE_EPISODE,
iState::COLUMN_WATCHED => 1,
iState::COLUMN_TITLE => 'Series Title',
iState::COLUMN_YEAR => '2020',
iState::COLUMN_SEASON => '1',
iState::COLUMN_EPISODE => '2',
iState::COLUMN_META_DATA_EXTRA => [
iState::COLUMN_META_DATA_EXTRA_DATE => '2020-01-03',
iState::COLUMN_META_DATA_EXTRA_TITLE => 'to test quorum',
],
iState::COLUMN_META_DATA_ADDED_AT => 1,
iState::COLUMN_META_DATA_PLAYED_AT => 2,
],
'home_emby' => [
iState::COLUMN_ID => 122,
iState::COLUMN_TYPE => iState::TYPE_EPISODE,
iState::COLUMN_WATCHED => 1,
iState::COLUMN_TITLE => 'Series Title',
iState::COLUMN_YEAR => '2020',
iState::COLUMN_SEASON => '1',
iState::COLUMN_EPISODE => '2',
iState::COLUMN_META_DATA_EXTRA => [
iState::COLUMN_META_DATA_EXTRA_DATE => '2020-01-03',
iState::COLUMN_META_DATA_EXTRA_TITLE => 'to test quorum',
],
iState::COLUMN_META_DATA_ADDED_AT => 1,
iState::COLUMN_META_DATA_PLAYED_AT => 2,
],
],
iState::COLUMN_EXTRA => [
'home_plex' => [
iState::COLUMN_EXTRA_DATE => 1,
iState::COLUMN_EXTRA_EVENT => 'media.scrobble'
],
'home_jellyfin' => [
iState::COLUMN_EXTRA_DATE => 1,
iState::COLUMN_EXTRA_EVENT => 'media.scrobble'
],
'home_emby' => [
iState::COLUMN_EXTRA_DATE => 1,
iState::COLUMN_EXTRA_EVENT => 'media.scrobble'
],
],
iState::COLUMN_CREATED_AT => 2,
iState::COLUMN_UPDATED_AT => 2,

View File

@@ -6,7 +6,10 @@ namespace Tests\Libs;
use App\Libs\Entity\StateEntity;
use App\Libs\Entity\StateInterface as iState;
use App\Libs\Extends\LogMessageProcessor;
use App\Libs\TestCase;
use Monolog\Handler\TestHandler;
use Monolog\Logger;
use RuntimeException;
class StateEntityTest extends TestCase
@@ -14,10 +17,16 @@ class StateEntityTest extends TestCase
private array $testMovie = [];
private array $testEpisode = [];
private TestHandler|null $lHandler = null;
private Logger|null $logger = null;
protected function setUp(): void
{
$this->testMovie = require __DIR__ . '/../Fixtures/MovieEntity.php';
$this->testEpisode = require __DIR__ . '/../Fixtures/EpisodeEntity.php';
$this->lHandler = new TestHandler();
$this->logger = new Logger('logger', processors: [new LogMessageProcessor()]);
$this->logger->pushHandler($this->lHandler);
}
public function test_init_bad_type(): void
@@ -893,4 +902,62 @@ class StateEntityTest extends TestCase
'When hasContext() is called with non-existing key, it returns false'
);
}
public function test_getMeta(): void
{
$real = $this->testEpisode;
$entity = new StateEntity($real);
$entity->via = '';
$this->assertSame(
'__not_set',
$entity->getMeta('extra.title', '__not_set'),
'When no via is set, returns the default value'
);
$real = $this->testEpisode;
unset($real['metadata']['home_jellyfin']);
$entity = new StateEntity($real);
$this->assertSame(
ag($real, 'metadata.home_plex.extra.title'),
$entity->getMeta('extra.title'),
'When quorum is not met returns the entity via backend metadata.'
);
$entity->via = 'home_emby';
$this->assertNotSame(
ag($real, 'metadata.home_plex.extra.title'),
$entity->getMeta('extra.title'),
'When quorum is not met returns the entity via backend metadata.'
);
$entity = new StateEntity($this->testEpisode);
$this->assertSame(
ag($this->testEpisode, 'metadata.home_jellyfin.extra.title'),
$entity->getMeta('extra.title'),
'When quorum is met for key return that value instead of the default via metadata.'
);
$entity = new StateEntity(
ag_set($this->testEpisode, 'metadata.home_jellyfin.extra.title', 'random')
);
$this->assertSame(
ag($real, 'metadata.home_plex.extra.title'),
$entity->getMeta('extra.title'),
'When no quorum for value reached, return default via metadata.'
);
$entity = new StateEntity(
ag_set($this->testEpisode, 'metadata.home_jellyfin.extra.title', null)
);
$this->assertSame(
ag($real, 'metadata.home_plex.extra.title'),
$entity->getMeta('extra.title'),
'Quorum will not be met if one of the values is null.'
);
}
}