Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed bug preventing emby/jellyfin from adding new backends. #361

Merged
merged 2 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use App\Libs\Extends\ConsoleOutput;
use App\Libs\Extends\HttpClient;
use App\Libs\Extends\LogMessageProcessor;
use App\Libs\Mappers\Import\DirectMapper;
use App\Libs\Mappers\Import\MemoryMapper;
use App\Libs\Mappers\ImportInterface as iImport;
use App\Libs\QueueRequests;
Expand Down Expand Up @@ -167,13 +168,26 @@
],

MemoryMapper::class => [
'class' => function (iLogger $logger, iDB $db): iImport {
return (new MemoryMapper(logger: $logger, db: $db))
'class' => function (iLogger $logger, iDB $db, CacheInterface $cache): iImport {
return (new MemoryMapper(logger: $logger, db: $db, cache: $cache))
->setOptions(options: Config::get('mapper.import.opts', []));
},
'args' => [
iLogger::class,
iDB::class,
CacheInterface::class
],
],

DirectMapper::class => [
'class' => function (iLogger $logger, iDB $db, CacheInterface $cache): iImport {
return (new DirectMapper(logger: $logger, db: $db, cache: $cache))
->setOptions(options: Config::get('mapper.import.opts', []));
},
'args' => [
iLogger::class,
iDB::class,
CacheInterface::class
],
],

Expand Down
2 changes: 1 addition & 1 deletion src/Backends/Emby/EmbyClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public function withContext(Context $context): self
'token' => $context->backendToken,
'app' => Config::get('name') . '/' . static::CLIENT_NAME,
'os' => PHP_OS,
'id' => md5($context->backendUser),
'id' => md5(Config::get('name') . '/' . static::CLIENT_NAME . $context->backendUser),
'version' => getAppVersion(),
'user' => $context->backendUser,
]
Expand Down
2 changes: 1 addition & 1 deletion src/Backends/Jellyfin/JellyfinClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public function withContext(Context $context): self
'token' => $context->backendToken,
'app' => Config::get('name') . '/' . static::CLIENT_NAME,
'os' => PHP_OS,
'id' => md5($context->backendUser),
'id' => md5(Config::get('name') . '/' . static::CLIENT_NAME . $context->backendUser),
'version' => getAppVersion(),
'user' => $context->backendUser,
]
Expand Down
3 changes: 1 addition & 2 deletions src/Backends/Jellyfin/JellyfinManage.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ public function manage(array $backend, array $opts = []): array
);

$question->setValidator(function ($answer) {
$host = parse_url($answer, PHP_URL_HOST);
if (false === is_string($answer)) {
if (false === isValidURL($answer)) {
throw new RuntimeException(
'Invalid backend URL was given. Expecting something like http://example.com:8096/'
);
Expand Down
3 changes: 1 addition & 2 deletions src/Backends/Plex/PlexManage.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,7 @@ public function manage(array $backend, array $opts = []): array
);

$question->setValidator(function ($answer) {
$host = parse_url($answer, PHP_URL_HOST);
if (false === is_string($answer)) {
if (false === isValidURL($answer)) {
throw new RuntimeException(
'Invalid URL was selected/given. Expecting something like http://plex:32400.'
);
Expand Down
74 changes: 72 additions & 2 deletions src/Commands/Config/AddCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@

use App\Command;
use App\Libs\Routable;
use RuntimeException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;

#[Routable(command: self::ROUTE)]
final class AddCommand extends Command
Expand All @@ -23,7 +26,7 @@ protected function configure(): void
$this->setName(self::ROUTE)
->setDescription('Add new backend.')
->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Use Alternative config file.')
->addArgument('backend', InputArgument::REQUIRED, 'Backend name')
->addArgument('backend', InputArgument::OPTIONAL, 'Backend name', null)
->setHelp(
r(
<<<HELP
Expand All @@ -50,6 +53,31 @@ protected function configure(): void
*/
protected function runCommand(InputInterface $input, OutputInterface $output): int
{
if (function_exists('stream_isatty') && defined('STDERR')) {
$tty = stream_isatty(STDERR);
} else {
$tty = true;
}

if (false === $tty || $input->getOption('no-interaction')) {
$output->writeln(
r(
<<<ERROR

<error>ERROR:</error> This command require <notice>interaction</notice>. For example:

{cmd} <cmd>{route}</cmd>

ERROR,
[
'cmd' => trim(commandContext()),
'route' => self::ROUTE,
]
)
);
return self::FAILURE;
}

$opts = [
'--add' => true,
];
Expand All @@ -61,7 +89,49 @@ protected function runCommand(InputInterface $input, OutputInterface $output): i
$opts['--' . $option] = $val;
}

$opts['backend'] = strtolower($input->getArgument('backend'));
$backend = $input->getArgument('backend');

if (null !== $backend) {
$opts['backend'] = strtolower($backend);
} else {
// -- $backend.token
$opts['backend'] = (function () use (&$opts, $input, $output) {
$chosen = ag($opts, 'backend');

$question = new Question(
<<<HELP
<question>What should we be calling this <value>backend</value>?</question>
------------------
Backend name is used to identify the backend. The backend name must only contains
<value>lower case a-z</value>, <value>0-9</value> and <value>_</value>.
------------------
<notice>Choose good name to identify your backend. For example, <value>home_plex</value>.</notice>
HELP. PHP_EOL . '> ',
$chosen
);

$question->setValidator(function ($answer) {
if (empty($answer)) {
throw new RuntimeException('Backend Name cannot be empty.');
}
if (!isValidName($answer) || strtolower($answer) !== $answer) {
throw new RuntimeException(
r(
'<error>ERROR:</error> Invalid [<value>{name}</value>] name was given. Only [<value>a-z, 0-9, _</value>] are allowed.',
[
'name' => $answer
],
)
);
}
return $answer;
});

return (new QuestionHelper())->ask($input, $output, $question);
})();
$output->writeln('');
$opts['backend'] = strtolower($opts['backend']);
}

return $this->getApplication()?->find(ManageCommand::ROUTE)->run(new ArrayInput($opts), $output) ?? 1;
}
Expand Down
1 change: 0 additions & 1 deletion src/Commands/Config/ManageCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ protected function runCommand(InputInterface $input, OutputInterface $output, nu
[
'cmd' => trim(commandContext()),
'route' => self::ROUTE,
'backend' => $input->getArgument('backend'),
]
)

Expand Down
2 changes: 1 addition & 1 deletion src/Commands/State/BackupCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ protected function process(InputInterface $input, OutputInterface $output): int
continue;
}

if (null === ($url = ag($backend, 'url')) || true !== is_string(parse_url($url, PHP_URL_HOST))) {
if (null === ($url = ag($backend, 'url')) || false === isValidURL($url)) {
$this->logger->error('SYSTEM: Ignoring [{backend}] because of invalid URL.', [
'backend' => $backendName,
'url' => $url ?? 'None',
Expand Down
2 changes: 1 addition & 1 deletion src/Commands/State/ExportCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ protected function process(InputInterface $input, OutputInterface $output): int
continue;
}

if (null === ($url = ag($backend, 'url')) || true !== is_string(parse_url($url, PHP_URL_HOST))) {
if (null === ($url = ag($backend, 'url')) || false === isValidURL($url)) {
$this->logger->error(
sprintf('%s: Backend does not have valid url.', $backendName),
['url' => $url ?? 'None']
Expand Down
5 changes: 3 additions & 2 deletions src/Commands/State/ImportCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use App\Commands\Backend\Library\UnmatchedCommand;
use App\Commands\Config\EditCommand;
use App\Libs\Config;
use App\Libs\Container;
use App\Libs\Database\DatabaseInterface as iDB;
use App\Libs\Entity\StateInterface as iState;
use App\Libs\Extends\StreamLogHandler;
Expand Down Expand Up @@ -250,7 +251,7 @@ protected function process(InputInterface $input, OutputInterface $output): int
}

if ($input->getOption('direct-mapper')) {
$this->mapper = new DirectMapper(logger: $this->logger, db: $this->db);
$this->mapper = Container::get(DirectMapper::class);
}

if (!empty($mapperOpts)) {
Expand Down Expand Up @@ -298,7 +299,7 @@ protected function process(InputInterface $input, OutputInterface $output): int
continue;
}

if (null === ($url = ag($backend, 'url')) || true !== is_string(parse_url($url, PHP_URL_HOST))) {
if (null === ($url = ag($backend, 'url')) || false === isValidURL($url)) {
$this->logger->error('SYSTEM: Ignoring [{backend}] because of invalid URL.', [
'backend' => $backendName,
'url' => $url ?? 'None',
Expand Down
7 changes: 5 additions & 2 deletions src/Commands/State/ProgressCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ protected function configure(): void
<error>***WARNING THIS COMMAND IS EXPERIMENTAL AND MAY NOT WORK AS EXPECTED***</error>
<notice>THIS COMMAND ONLY WORKS CORRECTLY FOR PLEX & EMBY AT THE MOMENT.</notice>
=================================================================================
Jellyfin API has a bug which i cannot do anything about.
Jellyfin API has a bug which I cannot do anything about.

This command push <notice>user</notice> watch progress to export enabled backends.
You should not run this manually and instead rely on scheduled task to run this command.
Expand Down Expand Up @@ -100,6 +100,9 @@ protected function process(InputInterface $input, OutputInterface $output): int
if (!empty($items)) {
foreach ($items as $queueItem) {
$dbItem = $this->db->get($queueItem);
if ($dbItem->isWatched()) {
continue;
}
$dbItem = $dbItem->apply($queueItem);

if (!$dbItem->hasPlayProgress()) {
Expand Down Expand Up @@ -146,7 +149,7 @@ protected function process(InputInterface $input, OutputInterface $output): int
continue;
}

if (null === ($url = ag($backend, 'url')) || true !== is_string(parse_url($url, PHP_URL_HOST))) {
if (null === ($url = ag($backend, 'url')) || false === isValidURL($url)) {
$this->logger->error('SYSTEM: [{backend}] Invalid url.', [
'backend' => $backendName,
'url' => $url ?? 'None',
Expand Down
2 changes: 1 addition & 1 deletion src/Commands/State/PushCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ protected function process(InputInterface $input): int
continue;
}

if (null === ($url = ag($backend, 'url')) || true !== is_string(parse_url($url, PHP_URL_HOST))) {
if (null === ($url = ag($backend, 'url')) || false === isValidURL($url)) {
$this->logger->error('SYSTEM: [{backend}] Invalid url.', [
'backend' => $backendName,
'url' => $url ?? 'None',
Expand Down
56 changes: 46 additions & 10 deletions src/Libs/Mappers/Import/DirectMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@
use App\Libs\Mappers\ImportInterface as iImport;
use App\Libs\Message;
use App\Libs\Options;
use DateInterval;
use DateTimeInterface as iDate;
use Exception;
use PDOException;
use Psr\Log\LoggerInterface as iLogger;
use Psr\SimpleCache\CacheInterface;
use Psr\SimpleCache\InvalidArgumentException;

final class DirectMapper implements iImport
{
Expand Down Expand Up @@ -43,8 +46,12 @@ final class DirectMapper implements iImport
protected array $options = [];

protected bool $fullyLoaded = false;
/**
* @var array<string,iState> List of items with play progress.
*/
protected array $progressItems = [];

public function __construct(protected iLogger $logger, protected iDB $db)
public function __construct(protected iLogger $logger, protected iDB $db, protected CacheInterface $cache)
{
}

Expand Down Expand Up @@ -297,8 +304,12 @@ public function add(iState $entity, array $opts = []): self
return $this;
}

$newPlayProgress = (int)ag($entity->getMetadata($entity->via), iState::COLUMN_META_DATA_PROGRESS);
$oldPlayProgress = (int)ag($cloned->getMetadata($entity->via), iState::COLUMN_META_DATA_PROGRESS);
$playChanged = $newPlayProgress != $oldPlayProgress;

// -- this sometimes leads to never ending updates as data from backends conflicts.
if (true === (bool)ag($this->options, Options::MAPPER_ALWAYS_UPDATE_META)) {
if ($playChanged || true === (bool)ag($this->options, Options::MAPPER_ALWAYS_UPDATE_META)) {
if (true === (clone $cloned)->apply(entity: $entity, fields: $keys)->isChanged(fields: $keys)) {
try {
$local = $local->apply(
Expand All @@ -311,16 +322,30 @@ public function add(iState $entity, array $opts = []): self
$changes = $local->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,
]);
$this->logger->notice(
$playChanged ? 'MAPPER: [{backend}] updated [{title}] due to play progress change.' : 'MAPPER: [{backend}] updated [{title}] metadata.',
[
'id' => $cloned->id,
'backend' => $entity->via,
'title' => $cloned->getName(),
'changes' => $changes,
]
);
}

if (false === $inDryRunMode) {
$this->db->update($local);

if (true === $entity->hasPlayProgress()) {
$itemId = r('{type}://{id}:{tainted}@{backend}', [
'type' => $entity->type,
'backend' => $entity->via,
'tainted' => $entity->isTainted() ? 'tainted' : 'untainted',
'id' => ag($entity->getMetadata($entity->via), iState::COLUMN_ID, '??'),
]);

$this->progressItems[$itemId] = $entity;
}
}

if (null === ($this->changed[$local->id] ?? null)) {
Expand Down Expand Up @@ -510,8 +535,19 @@ public function remove(iState $entity): bool
return $this->db->remove($entity);
}

public function commit(): mixed
public function commit(): array
{
if (count($this->progressItems) >= 1) {
try {
$progress = $this->cache->get('progress', []);
foreach ($this->progressItems as $itemId => $entity) {
$progress[$itemId] = $entity;
}
$this->cache->set('progress', $progress, new DateInterval('P1D'));
} catch (InvalidArgumentException) {
}
}

$list = $this->actions;

$this->reset();
Expand All @@ -532,7 +568,7 @@ public function reset(): self
];

$this->fullyLoaded = false;
$this->changed = $this->objects = $this->pointers = [];
$this->changed = $this->objects = $this->pointers = $this->progressItems = [];

return $this;
}
Expand Down
Loading
Loading