<commit_before><?php

declare(strict_types=1);

namespace App\Radio\Remote;

use App\Entity;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Uri;
use Monolog\Logger;
use NowPlaying\AdapterFactory;
use NowPlaying\Result\Result;

abstract class AbstractRemote
{
    public function __construct(
        protected EntityManagerInterface $em,
        protected Entity\Repository\SettingsRepository $settingsRepo,
        protected Client $http_client,
        protected Logger $logger,
        protected AdapterFactory $adapterFactory
    ) {
    }

    /**
     * @param Result $np
     * @param Entity\StationRemote $remote
     * @param bool $includeClients
     *
     * @return Result The aggregated now-playing result.
     */
    public function updateNowPlaying(
        Result $np,
        Entity\StationRemote $remote,
        bool $includeClients = false
    ): Result {
        $adapterType = $this->getAdapterType();

        $npAdapter = $this->adapterFactory->getAdapter(
            $adapterType,
            $remote->getUrl()
        );

        $npAdapter->setAdminPassword($remote->getAdminPassword());

        try {
            $result = $npAdapter->getNowPlaying($remote->getMount(), $includeClients);

            if (!empty($result->clients)) {
                foreach ($result->clients as $client) {
                    $client->mount = 'remote_' . $remote->getId();
                }
            }
        } catch (Exception $e) {
            $this->logger->error(sprintf('NowPlaying adapter error: %s', $e->getMessage()));

            $result = Result::blank();
        }

        $this->logger->debug('NowPlaying adapter response', ['response' => $result]);

        $remote->setListenersTotal($result->listeners->total);
        $remote->setListenersUnique($result->listeners->unique ?? 0);
        $this->em->persist($remote);
        $this->em->flush();

        return $np->merge($result);
    }

    abstract protected function getAdapterType(): string;

    /**
     * Return the likely "public" listen URL for the remote.
     *
     * @param Entity\StationRemote $remote
     */
    public function getPublicUrl(Entity\StationRemote $remote): string
    {
        $custom_listen_url = $remote->getCustomListenUrl();

        return (!empty($custom_listen_url))
            ? $custom_listen_url
            : $this->getRemoteUrl($remote, $remote->getMount());
    }

    /**
     * Format and return a URL for the remote path.
     *
     * @param Entity\StationRemote $remote
     * @param string|null $custom_path
     */
    protected function getRemoteUrl(Entity\StationRemote $remote, ?string $custom_path = null): string
    {
        $uri = new Uri($remote->getUrl());

        return (null !== $custom_path)
            ? (string)$uri->withPath($custom_path)
            : (string)$uri;
    }
}<commit_msg>Fix URI malforming issue with Remote Relays.<commit_after><?php

declare(strict_types=1);

namespace App\Radio\Remote;

use App\Entity;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use GuzzleHttp\Client;
use Monolog\Logger;
use NowPlaying\AdapterFactory;
use NowPlaying\Result\Result;

abstract class AbstractRemote
{
    public function __construct(
        protected EntityManagerInterface $em,
        protected Entity\Repository\SettingsRepository $settingsRepo,
        protected Client $http_client,
        protected Logger $logger,
        protected AdapterFactory $adapterFactory
    ) {
    }

    /**
     * @param Result $np
     * @param Entity\StationRemote $remote
     * @param bool $includeClients
     *
     * @return Result The aggregated now-playing result.
     */
    public function updateNowPlaying(
        Result $np,
        Entity\StationRemote $remote,
        bool $includeClients = false
    ): Result {
        $adapterType = $this->getAdapterType();

        $npAdapter = $this->adapterFactory->getAdapter(
            $adapterType,
            $remote->getUrl()
        );

        $npAdapter->setAdminPassword($remote->getAdminPassword());

        try {
            $result = $npAdapter->getNowPlaying($remote->getMount(), $includeClients);

            if (!empty($result->clients)) {
                foreach ($result->clients as $client) {
                    $client->mount = 'remote_' . $remote->getId();
                }
            }
        } catch (Exception $e) {
            $this->logger->error(sprintf('NowPlaying adapter error: %s', $e->getMessage()));

            $result = Result::blank();
        }

        $this->logger->debug('NowPlaying adapter response', ['response' => $result]);

        $remote->setListenersTotal($result->listeners->total);
        $remote->setListenersUnique($result->listeners->unique ?? 0);
        $this->em->persist($remote);
        $this->em->flush();

        return $np->merge($result);
    }

    abstract protected function getAdapterType(): string;

    /**
     * Return the likely "public" listen URL for the remote.
     *
     * @param Entity\StationRemote $remote
     */
    public function getPublicUrl(Entity\StationRemote $remote): string
    {
        $custom_listen_url = $remote->getCustomListenUrl();

        return (!empty($custom_listen_url))
            ? $custom_listen_url
            : $this->getRemoteUrl($remote, $remote->getMount());
    }

    /**
     * Format and return a URL for the remote path.
     *
     * @param Entity\StationRemote $remote
     * @param string|null $customPath
     */
    protected function getRemoteUrl(Entity\StationRemote $remote, ?string $customPath = null): string
    {
        $uri = $remote->getUrlAsUri();

        if (!empty($customPath)) {
            if (!str_starts_with($customPath, '/')) {
                $customPath = '/' . $customPath;
            }
            return (string)$uri->withPath($customPath);
        }

        return (string)$uri;
    }
}
{'fraction_non_alphanumeric': 0.1287981093855503, 'fraction_numerical': 0.0008440243079000675, 'mean_word_length': 2.4467713787085517, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 71, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1194510', 'n_tokens_mistral': 1631, 'n_tokens_neox': 1583, 'n_words': 346}