Added StateEntity::getMeta method to get metadata content based on quorum
This commit is contained in:
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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'] = [];
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user