Code quality cleanup sweep.
This commit is contained in:
parent
fe61967c50
commit
a9f066602c
|
@ -6,10 +6,9 @@ declare(strict_types=1);
|
||||||
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);
|
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);
|
||||||
ini_set('display_errors', '1');
|
ini_set('display_errors', '1');
|
||||||
|
|
||||||
$autoloader = require dirname(__DIR__) . '/vendor/autoload.php';
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
$cli = App\AppFactory::createCli(
|
$cli = App\AppFactory::createCli(
|
||||||
$autoloader,
|
|
||||||
[
|
[
|
||||||
App\Environment::BASE_DIR => dirname(__DIR__),
|
App\Environment::BASE_DIR => dirname(__DIR__),
|
||||||
]
|
]
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
"ext-intl": "*",
|
"ext-intl": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
|
"ext-libxml": "*",
|
||||||
"ext-maxminddb": "*",
|
"ext-maxminddb": "*",
|
||||||
"ext-mbstring": "*",
|
"ext-mbstring": "*",
|
||||||
"ext-redis": "*",
|
"ext-redis": "*",
|
||||||
|
|
|
@ -8,10 +8,8 @@ use App\Console\Application;
|
||||||
use App\Enums\SupportedLocales;
|
use App\Enums\SupportedLocales;
|
||||||
use App\Http\Factory\ResponseFactory;
|
use App\Http\Factory\ResponseFactory;
|
||||||
use App\Http\Factory\ServerRequestFactory;
|
use App\Http\Factory\ServerRequestFactory;
|
||||||
use Composer\Autoload\ClassLoader;
|
|
||||||
use DI;
|
use DI;
|
||||||
use DI\Bridge\Slim\ControllerInvoker;
|
use DI\Bridge\Slim\ControllerInvoker;
|
||||||
use Doctrine\Common\Annotations\AnnotationRegistry;
|
|
||||||
use Invoker\Invoker;
|
use Invoker\Invoker;
|
||||||
use Invoker\ParameterResolver\AssociativeArrayResolver;
|
use Invoker\ParameterResolver\AssociativeArrayResolver;
|
||||||
use Invoker\ParameterResolver\Container\TypeHintContainerResolver;
|
use Invoker\ParameterResolver\Container\TypeHintContainerResolver;
|
||||||
|
@ -31,33 +29,19 @@ use const E_USER_ERROR;
|
||||||
|
|
||||||
class AppFactory
|
class AppFactory
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @param ClassLoader|null $autoloader
|
|
||||||
* @param array<string, mixed> $appEnvironment
|
|
||||||
* @param array<string, mixed> $diDefinitions
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static function createApp(
|
public static function createApp(
|
||||||
?ClassLoader $autoloader = null,
|
|
||||||
array $appEnvironment = [],
|
array $appEnvironment = [],
|
||||||
array $diDefinitions = []
|
array $diDefinitions = []
|
||||||
): App {
|
): App {
|
||||||
$di = self::buildContainer($autoloader, $appEnvironment, $diDefinitions);
|
$di = self::buildContainer($appEnvironment, $diDefinitions);
|
||||||
return self::buildAppFromContainer($di);
|
return self::buildAppFromContainer($di);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ClassLoader|null $autoloader
|
|
||||||
* @param array<string, mixed> $appEnvironment
|
|
||||||
* @param array<string, mixed> $diDefinitions
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static function createCli(
|
public static function createCli(
|
||||||
?ClassLoader $autoloader = null,
|
|
||||||
array $appEnvironment = [],
|
array $appEnvironment = [],
|
||||||
array $diDefinitions = []
|
array $diDefinitions = []
|
||||||
): Application {
|
): Application {
|
||||||
$di = self::buildContainer($autoloader, $appEnvironment, $diDefinitions);
|
$di = self::buildContainer($appEnvironment, $diDefinitions);
|
||||||
self::buildAppFromContainer($di);
|
self::buildAppFromContainer($di);
|
||||||
|
|
||||||
$env = $di->get(Environment::class);
|
$env = $di->get(Environment::class);
|
||||||
|
@ -106,24 +90,10 @@ class AppFactory
|
||||||
return $app;
|
return $app;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ClassLoader|null $autoloader
|
|
||||||
* @param array<string, mixed> $appEnvironment
|
|
||||||
* @param array<string, mixed> $diDefinitions
|
|
||||||
*
|
|
||||||
* @noinspection SummerTimeUnsafeTimeManipulationInspection
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static function buildContainer(
|
public static function buildContainer(
|
||||||
?ClassLoader $autoloader = null,
|
|
||||||
array $appEnvironment = [],
|
array $appEnvironment = [],
|
||||||
array $diDefinitions = []
|
array $diDefinitions = []
|
||||||
): DI\Container {
|
): DI\Container {
|
||||||
// Register Annotation autoloader
|
|
||||||
if (null !== $autoloader) {
|
|
||||||
AnnotationRegistry::registerLoader([$autoloader, 'loadClass']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$environment = self::buildEnvironment($appEnvironment);
|
$environment = self::buildEnvironment($appEnvironment);
|
||||||
Environment::setInstance($environment);
|
Environment::setInstance($environment);
|
||||||
|
|
||||||
|
@ -132,22 +102,20 @@ class AppFactory
|
||||||
// Override DI definitions for settings.
|
// Override DI definitions for settings.
|
||||||
$diDefinitions[Environment::class] = $environment;
|
$diDefinitions[Environment::class] = $environment;
|
||||||
|
|
||||||
if ($autoloader) {
|
$plugins = new Plugins($environment->getBaseDirectory() . '/plugins');
|
||||||
$plugins = new Plugins($environment->getBaseDirectory() . '/plugins');
|
|
||||||
|
|
||||||
$diDefinitions[Plugins::class] = $plugins;
|
$diDefinitions[Plugins::class] = $plugins;
|
||||||
$diDefinitions = $plugins->registerServices($diDefinitions);
|
$diDefinitions = $plugins->registerServices($diDefinitions);
|
||||||
}
|
|
||||||
|
|
||||||
$containerBuilder = new DI\ContainerBuilder();
|
$containerBuilder = new DI\ContainerBuilder();
|
||||||
$containerBuilder->useAutowiring(true);
|
$containerBuilder->useAutowiring(true);
|
||||||
|
|
||||||
/*
|
// TODO Implement APCu
|
||||||
$containerBuilder->enableDefinitionCache();
|
// $containerBuilder->enableDefinitionCache();
|
||||||
|
|
||||||
if ($environment->isProduction()) {
|
if ($environment->isProduction()) {
|
||||||
$containerBuilder->enableCompilation($environment->getTempDirectory());
|
$containerBuilder->enableCompilation($environment->getTempDirectory());
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
$containerBuilder->addDefinitions($diDefinitions);
|
$containerBuilder->addDefinitions($diDefinitions);
|
||||||
|
|
||||||
|
|
|
@ -253,9 +253,9 @@ class Assets
|
||||||
|
|
||||||
$this->addInlineJs(
|
$this->addInlineJs(
|
||||||
<<<JS
|
<<<JS
|
||||||
let ${name};
|
let {$name};
|
||||||
$(function () {
|
$(function () {
|
||||||
${name} = ${nameWithoutPrefix}.default('${elementId}', ${propsJson});
|
{$name} = {$nameWithoutPrefix}.default('{$elementId}', {$propsJson});
|
||||||
});
|
});
|
||||||
JS
|
JS
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,6 +15,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
use Symfony\Component\Filesystem\Path;
|
use Symfony\Component\Filesystem\Path;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
use const PATHINFO_EXTENSION;
|
use const PATHINFO_EXTENSION;
|
||||||
|
|
||||||
|
@ -101,7 +102,7 @@ class BackupCommand extends AbstractBackupCommand
|
||||||
$tmp_dir_mariadb = '/tmp/azuracast_backup_mariadb';
|
$tmp_dir_mariadb = '/tmp/azuracast_backup_mariadb';
|
||||||
try {
|
try {
|
||||||
$fsUtils->mkdir($tmp_dir_mariadb);
|
$fsUtils->mkdir($tmp_dir_mariadb);
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$io->error($e->getMessage());
|
$io->error($e->getMessage());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ use App\Entity;
|
||||||
use App\Entity\Repository\StationRepository;
|
use App\Entity\Repository\StationRepository;
|
||||||
use App\Radio\Backend\Liquidsoap\Command\AbstractCommand;
|
use App\Radio\Backend\Liquidsoap\Command\AbstractCommand;
|
||||||
use App\Radio\Enums\LiquidsoapCommands;
|
use App\Radio\Enums\LiquidsoapCommands;
|
||||||
|
use LogicException;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
@ -17,6 +18,7 @@ use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
#[AsCommand(
|
#[AsCommand(
|
||||||
name: 'azuracast:internal:liquidsoap',
|
name: 'azuracast:internal:liquidsoap',
|
||||||
|
@ -62,12 +64,12 @@ class LiquidsoapCommand extends CommandAbstract
|
||||||
try {
|
try {
|
||||||
$station = $this->stationRepo->findByIdentifier($stationId);
|
$station = $this->stationRepo->findByIdentifier($stationId);
|
||||||
if (!($station instanceof Entity\Station)) {
|
if (!($station instanceof Entity\Station)) {
|
||||||
throw new \LogicException('Station not found.');
|
throw new LogicException('Station not found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$command = LiquidsoapCommands::tryFrom($action);
|
$command = LiquidsoapCommands::tryFrom($action);
|
||||||
if (null === $command || !$this->di->has($command->getClass())) {
|
if (null === $command || !$this->di->has($command->getClass())) {
|
||||||
throw new \LogicException('Command not found.');
|
throw new LogicException('Command not found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var AbstractCommand $commandObj */
|
/** @var AbstractCommand $commandObj */
|
||||||
|
@ -75,7 +77,7 @@ class LiquidsoapCommand extends CommandAbstract
|
||||||
|
|
||||||
$result = $commandObj->run($station, $asAutoDj, $payload);
|
$result = $commandObj->run($station, $asAutoDj, $payload);
|
||||||
$io->writeln($result);
|
$io->writeln($result);
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$this->logger->error(
|
$this->logger->error(
|
||||||
sprintf(
|
sprintf(
|
||||||
'Liquidsoap command "%s" error: %s',
|
'Liquidsoap command "%s" error: %s',
|
||||||
|
|
|
@ -65,7 +65,7 @@ class SftpEventCommand extends CommandAbstract
|
||||||
$storageLocation = $sftpUser->getStation()->getMediaStorageLocation();
|
$storageLocation = $sftpUser->getStation()->getMediaStorageLocation();
|
||||||
|
|
||||||
if (!$storageLocation->isLocal()) {
|
if (!$storageLocation->isLocal()) {
|
||||||
$this->logger->error(sprintf('Storage location "%s" is not local.', (string)$storageLocation));
|
$this->logger->error(sprintf('Storage location "%s" is not local.', $storageLocation));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
#[AsCommand(
|
#[AsCommand(
|
||||||
name: 'azuracast:radio:restart',
|
name: 'azuracast:radio:restart',
|
||||||
|
@ -73,7 +74,7 @@ class RestartRadioCommand extends CommandAbstract
|
||||||
reloadSupervisor: !$noSupervisorRestart,
|
reloadSupervisor: !$noSupervisorRestart,
|
||||||
forceRestart: true
|
forceRestart: true
|
||||||
);
|
);
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$io->error([
|
$io->error([
|
||||||
$station . ': ' . $e->getMessage(),
|
$station . ': ' . $e->getMessage(),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -12,6 +12,8 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Symfony\Component\Lock\Lock;
|
use Symfony\Component\Lock\Lock;
|
||||||
use Symfony\Component\Process\Process;
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
use function random_int;
|
||||||
|
|
||||||
abstract class AbstractSyncCommand extends CommandAbstract
|
abstract class AbstractSyncCommand extends CommandAbstract
|
||||||
{
|
{
|
||||||
protected array $processes = [];
|
protected array $processes = [];
|
||||||
|
@ -34,7 +36,7 @@ abstract class AbstractSyncCommand extends CommandAbstract
|
||||||
$process = $processGroup['process'];
|
$process = $processGroup['process'];
|
||||||
|
|
||||||
// 10% chance that refresh will be called
|
// 10% chance that refresh will be called
|
||||||
if (\random_int(1, 100) <= 10) {
|
if (random_int(1, 100) <= 10) {
|
||||||
$lock->refresh();
|
$lock->refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@ use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
use function random_int;
|
||||||
|
|
||||||
#[AsCommand(
|
#[AsCommand(
|
||||||
name: 'azuracast:sync:nowplaying',
|
name: 'azuracast:sync:nowplaying',
|
||||||
description: 'Task to run the Now Playing worker task.'
|
description: 'Task to run the Now Playing worker task.'
|
||||||
|
@ -81,7 +83,7 @@ class NowPlayingCommand extends AbstractSyncCommand
|
||||||
|
|
||||||
if (!isset($this->processes[$shortName])) {
|
if (!isset($this->processes[$shortName])) {
|
||||||
$npTimestamp = (int)$activeStation['nowplaying_timestamp'];
|
$npTimestamp = (int)$activeStation['nowplaying_timestamp'];
|
||||||
if (time() > $npTimestamp + \random_int(5, 15)) {
|
if (time() > $npTimestamp + random_int(5, 15)) {
|
||||||
$this->start($io, $shortName);
|
$this->start($io, $shortName);
|
||||||
|
|
||||||
usleep(250000);
|
usleep(250000);
|
||||||
|
|
|
@ -15,6 +15,7 @@ use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
#[AsCommand(
|
#[AsCommand(
|
||||||
name: 'azuracast:sync:nowplaying:station',
|
name: 'azuracast:sync:nowplaying:station',
|
||||||
|
@ -63,7 +64,7 @@ class NowPlayingPerStationCommand extends CommandAbstract
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->buildQueueTask->run($station);
|
$this->buildQueueTask->run($station);
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$this->logger->error(
|
$this->logger->error(
|
||||||
'Queue builder error: ' . $e->getMessage(),
|
'Queue builder error: ' . $e->getMessage(),
|
||||||
['exception' => $e]
|
['exception' => $e]
|
||||||
|
@ -72,7 +73,7 @@ class NowPlayingPerStationCommand extends CommandAbstract
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->nowPlayingTask->run($station);
|
$this->nowPlayingTask->run($station);
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$this->logger->error(
|
$this->logger->error(
|
||||||
'Now Playing error: ' . $e->getMessage(),
|
'Now Playing error: ' . $e->getMessage(),
|
||||||
['exception' => $e]
|
['exception' => $e]
|
||||||
|
|
|
@ -8,8 +8,10 @@ use App\Entity\Repository\SettingsRepository;
|
||||||
use App\Environment;
|
use App\Environment;
|
||||||
use App\Event\GetSyncTasks;
|
use App\Event\GetSyncTasks;
|
||||||
use App\LockFactory;
|
use App\LockFactory;
|
||||||
|
use App\Sync\Task\AbstractTask;
|
||||||
use Carbon\CarbonImmutable;
|
use Carbon\CarbonImmutable;
|
||||||
use Cron\CronExpression;
|
use Cron\CronExpression;
|
||||||
|
use DateTimeZone;
|
||||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
@ -17,6 +19,8 @@ use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
use function usleep;
|
||||||
|
|
||||||
#[AsCommand(
|
#[AsCommand(
|
||||||
name: 'azuracast:sync:run',
|
name: 'azuracast:sync:run',
|
||||||
description: 'Task to run the minute\'s synchronized tasks.'
|
description: 'Task to run the minute\'s synchronized tasks.'
|
||||||
|
@ -46,7 +50,7 @@ class RunnerCommand extends AbstractSyncCommand
|
||||||
$syncTasksEvent = new GetSyncTasks();
|
$syncTasksEvent = new GetSyncTasks();
|
||||||
$this->dispatcher->dispatch($syncTasksEvent);
|
$this->dispatcher->dispatch($syncTasksEvent);
|
||||||
|
|
||||||
$now = CarbonImmutable::now(new \DateTimeZone('UTC'));
|
$now = CarbonImmutable::now(new DateTimeZone('UTC'));
|
||||||
|
|
||||||
foreach ($syncTasksEvent->getTasks() as $taskClass) {
|
foreach ($syncTasksEvent->getTasks() as $taskClass) {
|
||||||
$schedulePattern = $taskClass::getSchedulePattern();
|
$schedulePattern = $taskClass::getSchedulePattern();
|
||||||
|
@ -71,12 +75,12 @@ class RunnerCommand extends AbstractSyncCommand
|
||||||
$this->checkRunningProcesses();
|
$this->checkRunningProcesses();
|
||||||
}
|
}
|
||||||
|
|
||||||
\usleep(250000);
|
usleep(250000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param SymfonyStyle $io
|
* @param SymfonyStyle $io
|
||||||
* @param class-string $taskClass
|
* @param class-string<AbstractTask> $taskClass
|
||||||
*/
|
*/
|
||||||
protected function start(
|
protected function start(
|
||||||
SymfonyStyle $io,
|
SymfonyStyle $io,
|
||||||
|
|
|
@ -6,9 +6,11 @@ namespace App\Console\Command\Sync;
|
||||||
|
|
||||||
use App\Console\Command\CommandAbstract;
|
use App\Console\Command\CommandAbstract;
|
||||||
use App\Sync\Task\AbstractTask;
|
use App\Sync\Task\AbstractTask;
|
||||||
|
use InvalidArgumentException;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
use Psr\SimpleCache\CacheInterface;
|
use Psr\SimpleCache\CacheInterface;
|
||||||
|
use ReflectionClass;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
@ -43,7 +45,7 @@ class SingleTaskCommand extends CommandAbstract
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->runTask($task);
|
$this->runTask($task);
|
||||||
} catch (\InvalidArgumentException $e) {
|
} catch (InvalidArgumentException $e) {
|
||||||
$io->error($e->getMessage());
|
$io->error($e->getMessage());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -60,12 +62,12 @@ class SingleTaskCommand extends CommandAbstract
|
||||||
bool $force = false
|
bool $force = false
|
||||||
): void {
|
): void {
|
||||||
if (!$this->di->has($task)) {
|
if (!$this->di->has($task)) {
|
||||||
throw new \InvalidArgumentException('Task not found.');
|
throw new InvalidArgumentException('Task not found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$taskClass = $this->di->get($task);
|
$taskClass = $this->di->get($task);
|
||||||
if (!($taskClass instanceof AbstractTask)) {
|
if (!($taskClass instanceof AbstractTask)) {
|
||||||
throw new \InvalidArgumentException('Specified class is not a synchronized task.');
|
throw new InvalidArgumentException('Specified class is not a synchronized task.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$taskShortName = self::getClassShortName($task);
|
$taskShortName = self::getClassShortName($task);
|
||||||
|
@ -106,6 +108,6 @@ class SingleTaskCommand extends CommandAbstract
|
||||||
*/
|
*/
|
||||||
public static function getClassShortName(string $taskClass): string
|
public static function getClassShortName(string $taskClass): string
|
||||||
{
|
{
|
||||||
return (new \ReflectionClass($taskClass))->getShortName();
|
return (new ReflectionClass($taskClass))->getShortName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ use App\Session\Flash;
|
||||||
use App\Sync\NowPlaying\Task\NowPlayingTask;
|
use App\Sync\NowPlaying\Task\NowPlayingTask;
|
||||||
use Carbon\CarbonImmutable;
|
use Carbon\CarbonImmutable;
|
||||||
use Cron\CronExpression;
|
use Cron\CronExpression;
|
||||||
|
use DateTimeZone;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Monolog\Handler\TestHandler;
|
use Monolog\Handler\TestHandler;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
|
@ -55,7 +56,7 @@ class DebugController extends AbstractLogViewerController
|
||||||
}
|
}
|
||||||
|
|
||||||
$syncTimes = [];
|
$syncTimes = [];
|
||||||
$now = CarbonImmutable::now(new \DateTimeZone('UTC'));
|
$now = CarbonImmutable::now(new DateTimeZone('UTC'));
|
||||||
$syncTasksEvent = new GetSyncTasks();
|
$syncTasksEvent = new GetSyncTasks();
|
||||||
$dispatcher->dispatch($syncTasksEvent);
|
$dispatcher->dispatch($syncTasksEvent);
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ namespace App\Controller\Admin;
|
||||||
use App\Http\Response;
|
use App\Http\Response;
|
||||||
use App\Http\ServerRequest;
|
use App\Http\ServerRequest;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
class ShoutcastAction
|
class ShoutcastAction
|
||||||
{
|
{
|
||||||
|
@ -15,7 +16,7 @@ class ShoutcastAction
|
||||||
Response $response
|
Response $response
|
||||||
): ResponseInterface {
|
): ResponseInterface {
|
||||||
if ('x86_64' !== php_uname('m')) {
|
if ('x86_64' !== php_uname('m')) {
|
||||||
throw new \RuntimeException('SHOUTcast cannot be installed on non-X86_64 systems.');
|
throw new RuntimeException('SHOUTcast cannot be installed on non-X86_64 systems.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$router = $request->getRouter();
|
$router = $request->getRouter();
|
||||||
|
|
|
@ -28,9 +28,9 @@ class StationsAction
|
||||||
props: array_merge(
|
props: array_merge(
|
||||||
$stationFormComponent->getProps($request),
|
$stationFormComponent->getProps($request),
|
||||||
[
|
[
|
||||||
'listUrl' => (string)$router->fromHere('api:admin:stations'),
|
'listUrl' => (string)$router->fromHere('api:admin:stations'),
|
||||||
'frontendTypes' => $adapters->listFrontendAdapters(false),
|
'frontendTypes' => $adapters->listFrontendAdapters(),
|
||||||
'backendTypes' => $adapters->listBackendAdapters(false),
|
'backendTypes' => $adapters->listBackendAdapters(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -21,6 +21,6 @@ class GetLogAction
|
||||||
): ResponseInterface {
|
): ResponseInterface {
|
||||||
$logPath = File::validateTempPath($path);
|
$logPath = File::validateTempPath($path);
|
||||||
|
|
||||||
return $this->streamLogToResponse($request, $response, $logPath, true);
|
return $this->streamLogToResponse($request, $response, $logPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ use App\Http\Response;
|
||||||
use App\Http\ServerRequest;
|
use App\Http\ServerRequest;
|
||||||
use App\Service\Flow;
|
use App\Service\Flow;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use RuntimeException;
|
||||||
use Symfony\Component\Process\Process;
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
class PostAction
|
class PostAction
|
||||||
|
@ -20,7 +21,7 @@ class PostAction
|
||||||
Environment $environment
|
Environment $environment
|
||||||
): ResponseInterface {
|
): ResponseInterface {
|
||||||
if ('x86_64' !== php_uname('m')) {
|
if ('x86_64' !== php_uname('m')) {
|
||||||
throw new \RuntimeException('SHOUTcast cannot be installed on non-X86_64 systems.');
|
throw new RuntimeException('SHOUTcast cannot be installed on non-X86_64 systems.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$flowResponse = Flow::process($request, $response);
|
$flowResponse = Flow::process($request, $response);
|
||||||
|
|
|
@ -12,6 +12,7 @@ use App\Http\ServerRequest;
|
||||||
use DeepCopy;
|
use DeepCopy;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class CloneAction extends StationsController
|
class CloneAction extends StationsController
|
||||||
{
|
{
|
||||||
|
@ -186,7 +187,7 @@ class CloneAction extends StationsController
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->configuration->writeConfiguration($newStation);
|
$this->configuration->writeConfiguration($newStation);
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->em->flush();
|
$this->em->flush();
|
||||||
|
|
|
@ -20,6 +20,7 @@ use Symfony\Component\Filesystem\Filesystem;
|
||||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
use Symfony\Component\Serializer\Serializer;
|
use Symfony\Component\Serializer\Serializer;
|
||||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
/** @extends AbstractAdminApiCrudController<Entity\Station> */
|
/** @extends AbstractAdminApiCrudController<Entity\Station> */
|
||||||
#[
|
#[
|
||||||
|
@ -331,7 +332,7 @@ class StationsController extends AbstractAdminApiCrudController
|
||||||
station: $station,
|
station: $station,
|
||||||
forceRestart: true
|
forceRestart: true
|
||||||
);
|
);
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ class DeleteTwoFactorAction extends UsersController
|
||||||
$user = $request->getUser();
|
$user = $request->getUser();
|
||||||
$user = $this->em->refetch($user);
|
$user = $this->em->refetch($user);
|
||||||
|
|
||||||
$user->setTwoFactorSecret(null);
|
$user->setTwoFactorSecret();
|
||||||
$this->em->persist($user);
|
$this->em->persist($user);
|
||||||
$this->em->flush();
|
$this->em->flush();
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,12 @@ use App\Http\Response;
|
||||||
use App\Http\ServerRequest;
|
use App\Http\ServerRequest;
|
||||||
use App\Radio\Backend\Liquidsoap\Command\AbstractCommand;
|
use App\Radio\Backend\Liquidsoap\Command\AbstractCommand;
|
||||||
use App\Radio\Enums\LiquidsoapCommands;
|
use App\Radio\Enums\LiquidsoapCommands;
|
||||||
|
use InvalidArgumentException;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use RuntimeException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class LiquidsoapAction
|
class LiquidsoapAction
|
||||||
{
|
{
|
||||||
|
@ -31,13 +34,13 @@ class LiquidsoapAction
|
||||||
if (!$acl->isAllowed(StationPermissions::View, $station->getIdRequired())) {
|
if (!$acl->isAllowed(StationPermissions::View, $station->getIdRequired())) {
|
||||||
$authKey = $request->getHeaderLine('X-Liquidsoap-Api-Key');
|
$authKey = $request->getHeaderLine('X-Liquidsoap-Api-Key');
|
||||||
if (!$station->validateAdapterApiKey($authKey)) {
|
if (!$station->validateAdapterApiKey($authKey)) {
|
||||||
throw new \RuntimeException('Invalid API key.');
|
throw new RuntimeException('Invalid API key.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$command = LiquidsoapCommands::tryFrom($action);
|
$command = LiquidsoapCommands::tryFrom($action);
|
||||||
if (null === $command || !$di->has($command->getClass())) {
|
if (null === $command || !$di->has($command->getClass())) {
|
||||||
throw new \InvalidArgumentException('Command not found.');
|
throw new InvalidArgumentException('Command not found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var AbstractCommand $commandObj */
|
/** @var AbstractCommand $commandObj */
|
||||||
|
@ -45,7 +48,7 @@ class LiquidsoapAction
|
||||||
|
|
||||||
$result = $commandObj->run($station, $asAutoDj, $payload);
|
$result = $commandObj->run($station, $asAutoDj, $payload);
|
||||||
$response->getBody()->write((string)$result);
|
$response->getBody()->write((string)$result);
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$logger->error(
|
$logger->error(
|
||||||
sprintf(
|
sprintf(
|
||||||
'Liquidsoap command "%s" error: %s',
|
'Liquidsoap command "%s" error: %s',
|
||||||
|
|
|
@ -24,6 +24,7 @@ use InvalidArgumentException;
|
||||||
use League\Flysystem\StorageAttributes;
|
use League\Flysystem\StorageAttributes;
|
||||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use RuntimeException;
|
||||||
use Symfony\Component\Messenger\MessageBus;
|
use Symfony\Component\Messenger\MessageBus;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
|
@ -306,7 +307,7 @@ class BatchAction
|
||||||
$result = $this->parseRequest($request, $fs, true);
|
$result = $this->parseRequest($request, $fs, true);
|
||||||
|
|
||||||
if (BackendAdapters::Liquidsoap !== $station->getBackendTypeEnum()) {
|
if (BackendAdapters::Liquidsoap !== $station->getBackendTypeEnum()) {
|
||||||
throw new \RuntimeException('This functionality can only be used on stations that use Liquidsoap.');
|
throw new RuntimeException('This functionality can only be used on stations that use Liquidsoap.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var Liquidsoap $backend */
|
/** @var Liquidsoap $backend */
|
||||||
|
@ -336,7 +337,7 @@ class BatchAction
|
||||||
|
|
||||||
$newQueue = Entity\StationQueue::fromMedia($station, $media);
|
$newQueue = Entity\StationQueue::fromMedia($station, $media);
|
||||||
$newQueue->setTimestampCued($cuedTimestamp);
|
$newQueue->setTimestampCued($cuedTimestamp);
|
||||||
$newQueue->setIsPlayed(true);
|
$newQueue->setIsPlayed();
|
||||||
$this->em->persist($newQueue);
|
$this->em->persist($newQueue);
|
||||||
|
|
||||||
$event = AnnotateNextSong::fromStationQueue($newQueue, true);
|
$event = AnnotateNextSong::fromStationQueue($newQueue, true);
|
||||||
|
|
|
@ -13,6 +13,7 @@ use App\Radio\Backend\Liquidsoap;
|
||||||
use App\Radio\Backend\Liquidsoap\ConfigWriter;
|
use App\Radio\Backend\Liquidsoap\ConfigWriter;
|
||||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class PutAction
|
class PutAction
|
||||||
{
|
{
|
||||||
|
@ -45,7 +46,7 @@ class PutAction
|
||||||
|
|
||||||
$config = $event->buildConfiguration();
|
$config = $event->buildConfiguration();
|
||||||
$liquidsoap->verifyConfig($config);
|
$liquidsoap->verifyConfig($config);
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
return $response->withStatus(500)->withJson(Entity\Api\Error::fromException($e));
|
return $response->withStatus(500)->withJson(Entity\Api\Error::fromException($e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -333,7 +333,7 @@ class PodcastEpisodesController extends AbstractApiCrudController
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
protected function viewRecord(object $record, ServerRequest $request): mixed
|
protected function viewRecord(object $record, ServerRequest $request): Entity\Api\PodcastEpisode
|
||||||
{
|
{
|
||||||
if (!($record instanceof Entity\PodcastEpisode)) {
|
if (!($record instanceof Entity\PodcastEpisode)) {
|
||||||
throw new InvalidArgumentException(sprintf('Record must be an instance of %s.', $this->entityClass));
|
throw new InvalidArgumentException(sprintf('Record must be an instance of %s.', $this->entityClass));
|
||||||
|
|
|
@ -268,16 +268,10 @@ class PodcastsController extends AbstractApiCrudController
|
||||||
*/
|
*/
|
||||||
protected function getRecord(Entity\Station $station, string $id): ?object
|
protected function getRecord(Entity\Station $station, string $id): ?object
|
||||||
{
|
{
|
||||||
$record = $this->podcastRepository->fetchPodcastForStation($station, $id);
|
return $this->podcastRepository->fetchPodcastForStation($station, $id);
|
||||||
return $record;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function viewRecord(object $record, ServerRequest $request): Entity\Api\Podcast
|
||||||
* @param Entity\Podcast $record
|
|
||||||
* @param ServerRequest $request
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
protected function viewRecord(object $record, ServerRequest $request): mixed
|
|
||||||
{
|
{
|
||||||
if (!($record instanceof Entity\Podcast)) {
|
if (!($record instanceof Entity\Podcast)) {
|
||||||
throw new InvalidArgumentException(sprintf('Record must be an instance of %s.', $this->entityClass));
|
throw new InvalidArgumentException(sprintf('Record must be an instance of %s.', $this->entityClass));
|
||||||
|
|
|
@ -179,7 +179,7 @@ class RemotesController extends AbstractStationApiCrudController
|
||||||
return $this->listPaginatedFromQuery($request, $response, $qb->getQuery());
|
return $this->listPaginatedFromQuery($request, $response, $qb->getQuery());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function viewRecord(object $record, ServerRequest $request): mixed
|
protected function viewRecord(object $record, ServerRequest $request): Entity\Api\StationRemote
|
||||||
{
|
{
|
||||||
if (!($record instanceof Entity\StationRemote)) {
|
if (!($record instanceof Entity\StationRemote)) {
|
||||||
throw new InvalidArgumentException(
|
throw new InvalidArgumentException(
|
||||||
|
|
|
@ -14,6 +14,7 @@ use App\Radio\Configuration;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
#[
|
#[
|
||||||
OA\Get(
|
OA\Get(
|
||||||
|
@ -136,7 +137,7 @@ class ServicesController
|
||||||
station: $station,
|
station: $station,
|
||||||
forceRestart: true
|
forceRestart: true
|
||||||
);
|
);
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
return $response->withJson(
|
return $response->withJson(
|
||||||
new Entity\Api\Error(
|
new Entity\Api\Error(
|
||||||
500,
|
500,
|
||||||
|
@ -159,7 +160,7 @@ class ServicesController
|
||||||
forceRestart: true,
|
forceRestart: true,
|
||||||
attemptReload: false
|
attemptReload: false
|
||||||
);
|
);
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
return $response->withJson(
|
return $response->withJson(
|
||||||
new Entity\Api\Error(
|
new Entity\Api\Error(
|
||||||
500,
|
500,
|
||||||
|
|
|
@ -37,8 +37,7 @@ class TestLogAction extends AbstractWebhooksAction
|
||||||
return $this->streamLogToResponse(
|
return $this->streamLogToResponse(
|
||||||
$request,
|
$request,
|
||||||
$response,
|
$response,
|
||||||
$tempPath,
|
$tempPath
|
||||||
true
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ class Customization
|
||||||
$this->user = $request->getAttribute(ServerRequest::ATTR_USER);
|
$this->user = $request->getAttribute(ServerRequest::ATTR_USER);
|
||||||
|
|
||||||
// Register current theme
|
// Register current theme
|
||||||
$this->theme = $this->determineTheme($request, false);
|
$this->theme = $this->determineTheme($request);
|
||||||
$this->publicTheme = $this->determineTheme($request, true);
|
$this->publicTheme = $this->determineTheme($request, true);
|
||||||
|
|
||||||
// Register locale
|
// Register locale
|
||||||
|
@ -114,7 +114,7 @@ class Customization
|
||||||
|
|
||||||
$publicCss .= <<<CSS
|
$publicCss .= <<<CSS
|
||||||
[data-theme] body.page-minimal {
|
[data-theme] body.page-minimal {
|
||||||
background-image: url('${backgroundUrl}');
|
background-image: url('{$backgroundUrl}');
|
||||||
}
|
}
|
||||||
CSS;
|
CSS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ class DecoratedEntityManager extends EntityManagerDecorator implements Reloadabl
|
||||||
public function __construct(callable $createEm)
|
public function __construct(callable $createEm)
|
||||||
{
|
{
|
||||||
parent::__construct($createEm());
|
parent::__construct($createEm());
|
||||||
$this->createEm = Closure::fromCallable($createEm);
|
$this->createEm = $createEm(...);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -67,9 +67,7 @@ class NowPlaying implements ResolvableUrlInterface
|
||||||
*/
|
*/
|
||||||
public function resolveUrls(UriInterface $base): void
|
public function resolveUrls(UriInterface $base): void
|
||||||
{
|
{
|
||||||
if ($this->station instanceof ResolvableUrlInterface) {
|
$this->station->resolveUrls($base);
|
||||||
$this->station->resolveUrls($base);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->now_playing instanceof ResolvableUrlInterface) {
|
if ($this->now_playing instanceof ResolvableUrlInterface) {
|
||||||
$this->now_playing->resolveUrls($base);
|
$this->now_playing->resolveUrls($base);
|
||||||
|
|
|
@ -60,8 +60,6 @@ class SongHistory implements ResolvableUrlInterface
|
||||||
*/
|
*/
|
||||||
public function resolveUrls(UriInterface $base): void
|
public function resolveUrls(UriInterface $base): void
|
||||||
{
|
{
|
||||||
if ($this->song instanceof ResolvableUrlInterface) {
|
$this->song->resolveUrls($base);
|
||||||
$this->song->resolveUrls($base);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,8 +55,6 @@ class StationQueue implements ResolvableUrlInterface
|
||||||
*/
|
*/
|
||||||
public function resolveUrls(UriInterface $base): void
|
public function resolveUrls(UriInterface $base): void
|
||||||
{
|
{
|
||||||
if ($this->song instanceof ResolvableUrlInterface) {
|
$this->song->resolveUrls($base);
|
||||||
$this->song->resolveUrls($base);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,11 +126,7 @@ class NowPlayingApiGenerator
|
||||||
?UriInterface $baseUri = null
|
?UriInterface $baseUri = null
|
||||||
): Entity\Api\NowPlaying\NowPlaying {
|
): Entity\Api\NowPlaying\NowPlaying {
|
||||||
$np = $station->getNowplaying();
|
$np = $station->getNowplaying();
|
||||||
if (null !== $np) {
|
return $np ?? $this->offlineApi($station, $baseUri);
|
||||||
return $np;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->offlineApi($station, $baseUri);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function offlineApi(
|
protected function offlineApi(
|
||||||
|
|
|
@ -5,9 +5,10 @@ declare(strict_types=1);
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use JsonSerializable;
|
||||||
|
|
||||||
#[ORM\Embeddable]
|
#[ORM\Embeddable]
|
||||||
class ListenerDevice implements \JsonSerializable
|
class ListenerDevice implements JsonSerializable
|
||||||
{
|
{
|
||||||
#[ORM\Column(length: 255)]
|
#[ORM\Column(length: 255)]
|
||||||
protected ?string $client = null;
|
protected ?string $client = null;
|
||||||
|
@ -57,7 +58,7 @@ class ListenerDevice implements \JsonSerializable
|
||||||
return $this->os_family;
|
return $this->os_family;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function jsonSerialize(): mixed
|
public function jsonSerialize(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'client' => $this->client,
|
'client' => $this->client,
|
||||||
|
|
|
@ -5,9 +5,10 @@ declare(strict_types=1);
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use JsonSerializable;
|
||||||
|
|
||||||
#[ORM\Embeddable]
|
#[ORM\Embeddable]
|
||||||
class ListenerLocation implements \JsonSerializable
|
class ListenerLocation implements JsonSerializable
|
||||||
{
|
{
|
||||||
#[ORM\Column(length: 255, nullable: false)]
|
#[ORM\Column(length: 255, nullable: false)]
|
||||||
protected string $description = 'Unknown';
|
protected string $description = 'Unknown';
|
||||||
|
@ -57,7 +58,7 @@ class ListenerLocation implements \JsonSerializable
|
||||||
return $this->lon;
|
return $this->lon;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function jsonSerialize(): mixed
|
public function jsonSerialize(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'description' => $this->description,
|
'description' => $this->description,
|
||||||
|
|
|
@ -17,17 +17,17 @@ final class Version20180425050351 extends AbstractMigration
|
||||||
*/
|
*/
|
||||||
public function up(Schema $schema): void
|
public function up(Schema $schema): void
|
||||||
{
|
{
|
||||||
$this->changeCharset('utf8mb4', 'utf8mb4_bin');
|
$this->changeCharset('utf8mb4_bin');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function changeCharset(string $charset, string $collate): void
|
private function changeCharset(string $collate): void
|
||||||
{
|
{
|
||||||
$sqlLines = [
|
$sqlLines = [
|
||||||
'ALTER TABLE `station_media` DROP FOREIGN KEY FK_32AADE3AA0BDB2F3',
|
'ALTER TABLE `station_media` DROP FOREIGN KEY FK_32AADE3AA0BDB2F3',
|
||||||
'ALTER TABLE `song_history` DROP FOREIGN KEY FK_2AD16164A0BDB2F3',
|
'ALTER TABLE `song_history` DROP FOREIGN KEY FK_2AD16164A0BDB2F3',
|
||||||
'ALTER TABLE `station_media` CONVERT TO CHARACTER SET ' . $charset . ' COLLATE ' . $collate,
|
'ALTER TABLE `station_media` CONVERT TO CHARACTER SET utf8mb4 COLLATE ' . $collate,
|
||||||
'ALTER TABLE `song_history` CONVERT TO CHARACTER SET ' . $charset . ' COLLATE ' . $collate,
|
'ALTER TABLE `song_history` CONVERT TO CHARACTER SET utf8mb4 COLLATE ' . $collate,
|
||||||
'ALTER TABLE `songs` CONVERT TO CHARACTER SET ' . $charset . ' COLLATE ' . $collate,
|
'ALTER TABLE `songs` CONVERT TO CHARACTER SET utf8mb4 COLLATE ' . $collate,
|
||||||
'ALTER TABLE `song_history` ADD CONSTRAINT FK_2AD16164A0BDB2F3 FOREIGN KEY (song_id) REFERENCES songs (id) ON DELETE CASCADE',
|
'ALTER TABLE `song_history` ADD CONSTRAINT FK_2AD16164A0BDB2F3 FOREIGN KEY (song_id) REFERENCES songs (id) ON DELETE CASCADE',
|
||||||
'ALTER TABLE `station_media` ADD CONSTRAINT FK_32AADE3AA0BDB2F3 FOREIGN KEY (song_id) REFERENCES songs (id) ON DELETE SET NULL',
|
'ALTER TABLE `station_media` ADD CONSTRAINT FK_32AADE3AA0BDB2F3 FOREIGN KEY (song_id) REFERENCES songs (id) ON DELETE SET NULL',
|
||||||
];
|
];
|
||||||
|
@ -42,6 +42,6 @@ final class Version20180425050351 extends AbstractMigration
|
||||||
*/
|
*/
|
||||||
public function down(Schema $schema): void
|
public function down(Schema $schema): void
|
||||||
{
|
{
|
||||||
$this->changeCharset('utf8mb4', 'utf8mb4_unicode_ci');
|
$this->changeCharset('utf8mb4_unicode_ci');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,10 @@ final class Version20180826043500 extends AbstractMigration
|
||||||
*/
|
*/
|
||||||
public function up(Schema $schema): void
|
public function up(Schema $schema): void
|
||||||
{
|
{
|
||||||
$this->changeCharset('utf8mb4', 'utf8mb4_general_ci');
|
$this->changeCharset('utf8mb4_general_ci');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function changeCharset(string $charset, string $collate): void
|
private function changeCharset(string $collate): void
|
||||||
{
|
{
|
||||||
$db_name = $this->connection->getDatabase() ?? 'azuracast';
|
$db_name = $this->connection->getDatabase() ?? 'azuracast';
|
||||||
|
|
||||||
|
@ -51,7 +51,9 @@ final class Version20180826043500 extends AbstractMigration
|
||||||
];
|
];
|
||||||
|
|
||||||
$sqlLines = [
|
$sqlLines = [
|
||||||
'ALTER DATABASE ' . $this->connection->quoteIdentifier($db_name) . ' CHARACTER SET = ' . $charset . ' COLLATE = ' . $collate,
|
'ALTER DATABASE ' . $this->connection->quoteIdentifier(
|
||||||
|
$db_name
|
||||||
|
) . ' CHARACTER SET = utf8mb4 COLLATE = ' . $collate,
|
||||||
'ALTER TABLE `song_history` DROP FOREIGN KEY FK_2AD16164A0BDB2F3',
|
'ALTER TABLE `song_history` DROP FOREIGN KEY FK_2AD16164A0BDB2F3',
|
||||||
'ALTER TABLE `station_media` DROP FOREIGN KEY FK_32AADE3AA0BDB2F3',
|
'ALTER TABLE `station_media` DROP FOREIGN KEY FK_32AADE3AA0BDB2F3',
|
||||||
];
|
];
|
||||||
|
@ -60,7 +62,11 @@ final class Version20180826043500 extends AbstractMigration
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($tables as $table_name) {
|
foreach ($tables as $table_name) {
|
||||||
$this->addSql('ALTER TABLE ' . $this->connection->quoteIdentifier($table_name) . ' CONVERT TO CHARACTER SET ' . $charset . ' COLLATE ' . $collate);
|
$this->addSql(
|
||||||
|
'ALTER TABLE ' . $this->connection->quoteIdentifier(
|
||||||
|
$table_name
|
||||||
|
) . ' CONVERT TO CHARACTER SET utf8mb4 COLLATE ' . $collate
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$sqlLines = [
|
$sqlLines = [
|
||||||
|
@ -77,6 +83,6 @@ final class Version20180826043500 extends AbstractMigration
|
||||||
*/
|
*/
|
||||||
public function down(Schema $schema): void
|
public function down(Schema $schema): void
|
||||||
{
|
{
|
||||||
$this->changeCharset('utf8mb4', 'utf8mb4_unicode_ci');
|
$this->changeCharset('utf8mb4_unicode_ci');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,25 +7,20 @@ namespace App\Entity\Migration;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20210620131126 extends AbstractMigration
|
final class Version20210620131126 extends AbstractMigration
|
||||||
{
|
{
|
||||||
public function getDescription(): string
|
public function getDescription(): string
|
||||||
{
|
{
|
||||||
return '';
|
return 'Add "max_listener_duration" to station_mounts table.';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
public function up(Schema $schema): void
|
||||||
{
|
{
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('ALTER TABLE station_mounts ADD max_listener_duration INT NOT NULL');
|
$this->addSql('ALTER TABLE station_mounts ADD max_listener_duration INT NOT NULL');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
public function down(Schema $schema): void
|
||||||
{
|
{
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('ALTER TABLE station_mounts DROP max_listener_duration');
|
$this->addSql('ALTER TABLE station_mounts DROP max_listener_duration');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,26 +7,21 @@ namespace App\Entity\Migration;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20210703185549 extends AbstractMigration
|
final class Version20210703185549 extends AbstractMigration
|
||||||
{
|
{
|
||||||
public function getDescription(): string
|
public function getDescription(): string
|
||||||
{
|
{
|
||||||
return '';
|
return 'Add columns to facilitate "loop once" functionality.';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
public function up(Schema $schema): void
|
||||||
{
|
{
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('ALTER TABLE station_playlists ADD queue_reset_at INT NOT NULL');
|
$this->addSql('ALTER TABLE station_playlists ADD queue_reset_at INT NOT NULL');
|
||||||
$this->addSql('ALTER TABLE station_schedules ADD loop_once TINYINT(1) NOT NULL');
|
$this->addSql('ALTER TABLE station_schedules ADD loop_once TINYINT(1) NOT NULL');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
public function down(Schema $schema): void
|
||||||
{
|
{
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('ALTER TABLE station_playlists DROP queue_reset_at');
|
$this->addSql('ALTER TABLE station_playlists DROP queue_reset_at');
|
||||||
$this->addSql('ALTER TABLE station_schedules DROP loop_once');
|
$this->addSql('ALTER TABLE station_schedules DROP loop_once');
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,25 +7,20 @@ namespace App\Entity\Migration;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20210805004608 extends AbstractMigration
|
final class Version20210805004608 extends AbstractMigration
|
||||||
{
|
{
|
||||||
public function getDescription(): string
|
public function getDescription(): string
|
||||||
{
|
{
|
||||||
return '';
|
return 'Add author and e-mail to podcast table.';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
public function up(Schema $schema): void
|
||||||
{
|
{
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('ALTER TABLE podcast ADD author VARCHAR(255) NOT NULL, ADD email VARCHAR(255) NOT NULL');
|
$this->addSql('ALTER TABLE podcast ADD author VARCHAR(255) NOT NULL, ADD email VARCHAR(255) NOT NULL');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
public function down(Schema $schema): void
|
||||||
{
|
{
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('ALTER TABLE podcast DROP author, DROP email');
|
$this->addSql('ALTER TABLE podcast DROP author, DROP email');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,26 +7,21 @@ namespace App\Entity\Migration;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20211227232320 extends AbstractMigration
|
final class Version20211227232320 extends AbstractMigration
|
||||||
{
|
{
|
||||||
public function getDescription(): string
|
public function getDescription(): string
|
||||||
{
|
{
|
||||||
return '';
|
return 'Change on-delete behavior of media on song_history table.';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
public function up(Schema $schema): void
|
||||||
{
|
{
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('ALTER TABLE song_history DROP FOREIGN KEY FK_2AD16164EA9FDD75');
|
$this->addSql('ALTER TABLE song_history DROP FOREIGN KEY FK_2AD16164EA9FDD75');
|
||||||
$this->addSql('ALTER TABLE song_history ADD CONSTRAINT FK_2AD16164EA9FDD75 FOREIGN KEY (media_id) REFERENCES station_media (id) ON DELETE SET NULL');
|
$this->addSql('ALTER TABLE song_history ADD CONSTRAINT FK_2AD16164EA9FDD75 FOREIGN KEY (media_id) REFERENCES station_media (id) ON DELETE SET NULL');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
public function down(Schema $schema): void
|
||||||
{
|
{
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('ALTER TABLE song_history DROP FOREIGN KEY FK_2AD16164EA9FDD75');
|
$this->addSql('ALTER TABLE song_history DROP FOREIGN KEY FK_2AD16164EA9FDD75');
|
||||||
$this->addSql('ALTER TABLE song_history ADD CONSTRAINT FK_2AD16164EA9FDD75 FOREIGN KEY (media_id) REFERENCES station_media (id) ON DELETE CASCADE');
|
$this->addSql('ALTER TABLE song_history ADD CONSTRAINT FK_2AD16164EA9FDD75 FOREIGN KEY (media_id) REFERENCES station_media (id) ON DELETE CASCADE');
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ use Doctrine\DBAL\Connection;
|
||||||
use NowPlaying\Result\Client;
|
use NowPlaying\Result\Client;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Serializer\Serializer;
|
use Symfony\Component\Serializer\Serializer;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @extends Repository<Entity\Listener>
|
* @extends Repository<Entity\Listener>
|
||||||
|
@ -188,7 +189,7 @@ class ListenerRepository extends Repository
|
||||||
$record['device_is_bot'] = $browserResult->isBot ? 1 : 0;
|
$record['device_is_bot'] = $browserResult->isBot ? 1 : 0;
|
||||||
$record['device_browser_family'] = $this->truncateNullableString($browserResult->browserFamily, 150);
|
$record['device_browser_family'] = $this->truncateNullableString($browserResult->browserFamily, 150);
|
||||||
$record['device_os_family'] = $this->truncateNullableString($browserResult->osFamily, 150);
|
$record['device_os_family'] = $this->truncateNullableString($browserResult->osFamily, 150);
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$this->logger->error('Device Detector error: ' . $e->getMessage(), [
|
$this->logger->error('Device Detector error: ' . $e->getMessage(), [
|
||||||
'user_agent' => $userAgent,
|
'user_agent' => $userAgent,
|
||||||
'exception' => $e,
|
'exception' => $e,
|
||||||
|
@ -211,7 +212,7 @@ class ListenerRepository extends Repository
|
||||||
$record['location_country'] = $this->truncateNullableString($ipInfo->country, 2);
|
$record['location_country'] = $this->truncateNullableString($ipInfo->country, 2);
|
||||||
$record['location_lat'] = $ipInfo->lat;
|
$record['location_lat'] = $ipInfo->lat;
|
||||||
$record['location_lon'] = $ipInfo->lon;
|
$record['location_lon'] = $ipInfo->lon;
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$this->logger->error('IP Geolocation error: ' . $e->getMessage(), [
|
$this->logger->error('IP Geolocation error: ' . $e->getMessage(), [
|
||||||
'ip' => $ip,
|
'ip' => $ip,
|
||||||
'exception' => $e,
|
'exception' => $e,
|
||||||
|
|
|
@ -191,9 +191,9 @@ class StationPlaylistMediaRepository extends Repository
|
||||||
)->setParameter('playlist', $playlist)
|
)->setParameter('playlist', $playlist)
|
||||||
->execute();
|
->execute();
|
||||||
} elseif (Entity\Enums\PlaylistOrders::Shuffle === $playlist->getOrderEnum()) {
|
} elseif (Entity\Enums\PlaylistOrders::Shuffle === $playlist->getOrderEnum()) {
|
||||||
$this->em->transactional(
|
$this->em->wrapInTransaction(
|
||||||
function () use ($playlist): void {
|
function (ReloadableEntityManagerInterface $em) use ($playlist): void {
|
||||||
$allSpmRecordsQuery = $this->em->createQuery(
|
$allSpmRecordsQuery = $em->createQuery(
|
||||||
<<<'DQL'
|
<<<'DQL'
|
||||||
SELECT spm.id
|
SELECT spm.id
|
||||||
FROM App\Entity\StationPlaylistMedia spm
|
FROM App\Entity\StationPlaylistMedia spm
|
||||||
|
@ -202,7 +202,7 @@ class StationPlaylistMediaRepository extends Repository
|
||||||
DQL
|
DQL
|
||||||
)->setParameter('playlist', $playlist);
|
)->setParameter('playlist', $playlist);
|
||||||
|
|
||||||
$updateSpmWeightQuery = $this->em->createQuery(
|
$updateSpmWeightQuery = $em->createQuery(
|
||||||
<<<'DQL'
|
<<<'DQL'
|
||||||
UPDATE App\Entity\StationPlaylistMedia spm
|
UPDATE App\Entity\StationPlaylistMedia spm
|
||||||
SET spm.weight=:weight, spm.is_queued=1
|
SET spm.weight=:weight, spm.is_queued=1
|
||||||
|
|
|
@ -166,11 +166,7 @@ class StationRepository extends Repository
|
||||||
}
|
}
|
||||||
|
|
||||||
$customUrl = $this->settingsRepo->readSettings()->getDefaultAlbumArtUrlAsUri();
|
$customUrl = $this->settingsRepo->readSettings()->getDefaultAlbumArtUrlAsUri();
|
||||||
if (null !== $customUrl) {
|
return $customUrl ?? AssetFactory::createAlbumArt($this->environment)->getUri();
|
||||||
return $customUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
return AssetFactory::createAlbumArt($this->environment)->getUri();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setFallback(
|
public function setFallback(
|
||||||
|
|
|
@ -12,6 +12,7 @@ use App\Service\Avatar;
|
||||||
use App\Utilities\Urls;
|
use App\Utilities\Urls;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use GuzzleHttp\Psr7\Uri;
|
use GuzzleHttp\Psr7\Uri;
|
||||||
|
use InvalidArgumentException;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
use Psr\Http\Message\UriInterface;
|
use Psr\Http\Message\UriInterface;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
@ -245,7 +246,7 @@ class Settings implements Stringable
|
||||||
public function setAnalytics(?string $analytics): void
|
public function setAnalytics(?string $analytics): void
|
||||||
{
|
{
|
||||||
if (null !== $analytics && null === Entity\Enums\AnalyticsLevel::tryFrom($analytics)) {
|
if (null !== $analytics && null === Entity\Enums\AnalyticsLevel::tryFrom($analytics)) {
|
||||||
throw new \InvalidArgumentException('Invalid analytics level.');
|
throw new InvalidArgumentException('Invalid analytics level.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->analytics = $analytics;
|
$this->analytics = $analytics;
|
||||||
|
@ -337,7 +338,7 @@ class Settings implements Stringable
|
||||||
public function setPublicTheme(?string $publicTheme): void
|
public function setPublicTheme(?string $publicTheme): void
|
||||||
{
|
{
|
||||||
if (null !== $publicTheme && null === SupportedThemes::tryFrom($publicTheme)) {
|
if (null !== $publicTheme && null === SupportedThemes::tryFrom($publicTheme)) {
|
||||||
throw new \InvalidArgumentException('Unsupported theme specified.');
|
throw new InvalidArgumentException('Unsupported theme specified.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->public_theme = $publicTheme;
|
$this->public_theme = $publicTheme;
|
||||||
|
|
|
@ -19,10 +19,12 @@ use DateTimeZone;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use InvalidArgumentException;
|
||||||
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
|
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
|
||||||
use League\Flysystem\Visibility;
|
use League\Flysystem\Visibility;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
use Psr\Http\Message\UriInterface;
|
use Psr\Http\Message\UriInterface;
|
||||||
|
use RuntimeException;
|
||||||
use Stringable;
|
use Stringable;
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
use Symfony\Component\Filesystem\Path;
|
use Symfony\Component\Filesystem\Path;
|
||||||
|
@ -456,7 +458,7 @@ class Station implements Stringable, IdentifiableEntityInterface
|
||||||
public function setFrontendType(?string $frontend_type = null): void
|
public function setFrontendType(?string $frontend_type = null): void
|
||||||
{
|
{
|
||||||
if (null !== $frontend_type && null === FrontendAdapters::tryFrom($frontend_type)) {
|
if (null !== $frontend_type && null === FrontendAdapters::tryFrom($frontend_type)) {
|
||||||
throw new \InvalidArgumentException('Invalid frontend type specified.');
|
throw new InvalidArgumentException('Invalid frontend type specified.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->frontend_type = $frontend_type;
|
$this->frontend_type = $frontend_type;
|
||||||
|
@ -511,7 +513,7 @@ class Station implements Stringable, IdentifiableEntityInterface
|
||||||
public function setBackendType(string $backend_type = null): void
|
public function setBackendType(string $backend_type = null): void
|
||||||
{
|
{
|
||||||
if (null !== $backend_type && null === BackendAdapters::tryFrom($backend_type)) {
|
if (null !== $backend_type && null === BackendAdapters::tryFrom($backend_type)) {
|
||||||
throw new \InvalidArgumentException('Invalid frontend type specified.');
|
throw new InvalidArgumentException('Invalid frontend type specified.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->backend_type = $backend_type;
|
$this->backend_type = $backend_type;
|
||||||
|
@ -962,7 +964,7 @@ class Station implements Stringable, IdentifiableEntityInterface
|
||||||
public function getMediaStorageLocation(): StorageLocation
|
public function getMediaStorageLocation(): StorageLocation
|
||||||
{
|
{
|
||||||
if (null === $this->media_storage_location) {
|
if (null === $this->media_storage_location) {
|
||||||
throw new \RuntimeException('Media storage location not initialized.');
|
throw new RuntimeException('Media storage location not initialized.');
|
||||||
}
|
}
|
||||||
return $this->media_storage_location;
|
return $this->media_storage_location;
|
||||||
}
|
}
|
||||||
|
@ -970,7 +972,7 @@ class Station implements Stringable, IdentifiableEntityInterface
|
||||||
public function setMediaStorageLocation(?StorageLocation $storageLocation = null): void
|
public function setMediaStorageLocation(?StorageLocation $storageLocation = null): void
|
||||||
{
|
{
|
||||||
if (null !== $storageLocation && StorageLocationTypes::StationMedia !== $storageLocation->getTypeEnum()) {
|
if (null !== $storageLocation && StorageLocationTypes::StationMedia !== $storageLocation->getTypeEnum()) {
|
||||||
throw new \RuntimeException('Invalid storage location.');
|
throw new RuntimeException('Invalid storage location.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->media_storage_location = $storageLocation;
|
$this->media_storage_location = $storageLocation;
|
||||||
|
@ -979,7 +981,7 @@ class Station implements Stringable, IdentifiableEntityInterface
|
||||||
public function getRecordingsStorageLocation(): StorageLocation
|
public function getRecordingsStorageLocation(): StorageLocation
|
||||||
{
|
{
|
||||||
if (null === $this->recordings_storage_location) {
|
if (null === $this->recordings_storage_location) {
|
||||||
throw new \RuntimeException('Recordings storage location not initialized.');
|
throw new RuntimeException('Recordings storage location not initialized.');
|
||||||
}
|
}
|
||||||
return $this->recordings_storage_location;
|
return $this->recordings_storage_location;
|
||||||
}
|
}
|
||||||
|
@ -987,7 +989,7 @@ class Station implements Stringable, IdentifiableEntityInterface
|
||||||
public function setRecordingsStorageLocation(?StorageLocation $storageLocation = null): void
|
public function setRecordingsStorageLocation(?StorageLocation $storageLocation = null): void
|
||||||
{
|
{
|
||||||
if (null !== $storageLocation && StorageLocationTypes::StationRecordings !== $storageLocation->getTypeEnum()) {
|
if (null !== $storageLocation && StorageLocationTypes::StationRecordings !== $storageLocation->getTypeEnum()) {
|
||||||
throw new \RuntimeException('Invalid storage location.');
|
throw new RuntimeException('Invalid storage location.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->recordings_storage_location = $storageLocation;
|
$this->recordings_storage_location = $storageLocation;
|
||||||
|
@ -996,7 +998,7 @@ class Station implements Stringable, IdentifiableEntityInterface
|
||||||
public function getPodcastsStorageLocation(): StorageLocation
|
public function getPodcastsStorageLocation(): StorageLocation
|
||||||
{
|
{
|
||||||
if (null === $this->podcasts_storage_location) {
|
if (null === $this->podcasts_storage_location) {
|
||||||
throw new \RuntimeException('Podcasts storage location not initialized.');
|
throw new RuntimeException('Podcasts storage location not initialized.');
|
||||||
}
|
}
|
||||||
return $this->podcasts_storage_location;
|
return $this->podcasts_storage_location;
|
||||||
}
|
}
|
||||||
|
@ -1004,7 +1006,7 @@ class Station implements Stringable, IdentifiableEntityInterface
|
||||||
public function setPodcastsStorageLocation(?StorageLocation $storageLocation = null): void
|
public function setPodcastsStorageLocation(?StorageLocation $storageLocation = null): void
|
||||||
{
|
{
|
||||||
if (null !== $storageLocation && StorageLocationTypes::StationPodcasts !== $storageLocation->getTypeEnum()) {
|
if (null !== $storageLocation && StorageLocationTypes::StationPodcasts !== $storageLocation->getTypeEnum()) {
|
||||||
throw new \RuntimeException('Invalid storage location.');
|
throw new RuntimeException('Invalid storage location.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->podcasts_storage_location = $storageLocation;
|
$this->podcasts_storage_location = $storageLocation;
|
||||||
|
@ -1016,7 +1018,7 @@ class Station implements Stringable, IdentifiableEntityInterface
|
||||||
StorageLocationTypes::StationMedia => $this->getMediaStorageLocation(),
|
StorageLocationTypes::StationMedia => $this->getMediaStorageLocation(),
|
||||||
StorageLocationTypes::StationRecordings => $this->getRecordingsStorageLocation(),
|
StorageLocationTypes::StationRecordings => $this->getRecordingsStorageLocation(),
|
||||||
StorageLocationTypes::StationPodcasts => $this->getPodcastsStorageLocation(),
|
StorageLocationTypes::StationPodcasts => $this->getPodcastsStorageLocation(),
|
||||||
default => throw new \InvalidArgumentException('Invalid storage location.')
|
default => throw new InvalidArgumentException('Invalid storage location.')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ namespace App\Entity;
|
||||||
use App\Entity\Enums\StationBackendPerformanceModes;
|
use App\Entity\Enums\StationBackendPerformanceModes;
|
||||||
use App\Radio\Enums\StreamFormats;
|
use App\Radio\Enums\StreamFormats;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
class StationBackendConfiguration extends ArrayCollection
|
class StationBackendConfiguration extends ArrayCollection
|
||||||
{
|
{
|
||||||
|
@ -80,7 +81,7 @@ class StationBackendConfiguration extends ArrayCollection
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null !== $format && null === StreamFormats::tryFrom($format)) {
|
if (null !== $format && null === StreamFormats::tryFrom($format)) {
|
||||||
throw new \InvalidArgumentException('Invalid recording type specified.');
|
throw new InvalidArgumentException('Invalid recording type specified.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->set(self::RECORD_STREAMS_FORMAT, $format);
|
$this->set(self::RECORD_STREAMS_FORMAT, $format);
|
||||||
|
|
|
@ -13,6 +13,7 @@ use Azura\Normalizer\Attributes\DeepNormalize;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use InvalidArgumentException;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
use Stringable;
|
use Stringable;
|
||||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||||
|
@ -251,7 +252,7 @@ class StationPlaylist implements
|
||||||
public function setType(string $type): void
|
public function setType(string $type): void
|
||||||
{
|
{
|
||||||
if (null === PlaylistTypes::tryFrom($type)) {
|
if (null === PlaylistTypes::tryFrom($type)) {
|
||||||
throw new \InvalidArgumentException('Invalid playlist type.');
|
throw new InvalidArgumentException('Invalid playlist type.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->type = $type;
|
$this->type = $type;
|
||||||
|
@ -270,7 +271,7 @@ class StationPlaylist implements
|
||||||
public function setSource(string $source): void
|
public function setSource(string $source): void
|
||||||
{
|
{
|
||||||
if (null === PlaylistSources::tryFrom($source)) {
|
if (null === PlaylistSources::tryFrom($source)) {
|
||||||
throw new \InvalidArgumentException('Invalid playlist source.');
|
throw new InvalidArgumentException('Invalid playlist source.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->source = $source;
|
$this->source = $source;
|
||||||
|
@ -289,7 +290,7 @@ class StationPlaylist implements
|
||||||
public function setOrder(string $order): void
|
public function setOrder(string $order): void
|
||||||
{
|
{
|
||||||
if (null === PlaylistOrders::tryFrom($order)) {
|
if (null === PlaylistOrders::tryFrom($order)) {
|
||||||
throw new \InvalidArgumentException('Invalid playlist order.');
|
throw new InvalidArgumentException('Invalid playlist order.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->order = $order;
|
$this->order = $order;
|
||||||
|
@ -318,7 +319,7 @@ class StationPlaylist implements
|
||||||
public function setRemoteType(?string $remote_type): void
|
public function setRemoteType(?string $remote_type): void
|
||||||
{
|
{
|
||||||
if (null !== $remote_type && null === PlaylistRemoteTypes::tryFrom($remote_type)) {
|
if (null !== $remote_type && null === PlaylistRemoteTypes::tryFrom($remote_type)) {
|
||||||
throw new \InvalidArgumentException('Invalid playlist remote type.');
|
throw new InvalidArgumentException('Invalid playlist remote type.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->remote_type = $remote_type;
|
$this->remote_type = $remote_type;
|
||||||
|
|
|
@ -12,6 +12,7 @@ use App\Radio\Remote\AbstractRemote;
|
||||||
use App\Utilities;
|
use App\Utilities;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use GuzzleHttp\Psr7\Uri;
|
use GuzzleHttp\Psr7\Uri;
|
||||||
|
use InvalidArgumentException;
|
||||||
use Psr\Http\Message\UriInterface;
|
use Psr\Http\Message\UriInterface;
|
||||||
use Stringable;
|
use Stringable;
|
||||||
|
|
||||||
|
@ -234,7 +235,7 @@ class StationRemote implements
|
||||||
public function setType(string $type): void
|
public function setType(string $type): void
|
||||||
{
|
{
|
||||||
if (null === RemoteAdapters::tryFrom($type)) {
|
if (null === RemoteAdapters::tryFrom($type)) {
|
||||||
throw new \InvalidArgumentException('Invalid type specified.');
|
throw new InvalidArgumentException('Invalid type specified.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->type = $type;
|
$this->type = $type;
|
||||||
|
|
|
@ -222,7 +222,7 @@ class StationSchedule implements IdentifiableEntityInterface
|
||||||
7 => 'Sun',
|
7 => 'Sun',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (null !== $days) {
|
if ([] !== $days) {
|
||||||
$displayDays = [];
|
$displayDays = [];
|
||||||
foreach ($days as $day) {
|
foreach ($days as $day) {
|
||||||
$displayDays[] = $daysOfWeek[$day];
|
$displayDays[] = $daysOfWeek[$day];
|
||||||
|
|
|
@ -17,7 +17,7 @@ trait HasSongFields
|
||||||
use TruncateStrings;
|
use TruncateStrings;
|
||||||
|
|
||||||
#[
|
#[
|
||||||
OA\Property(),
|
OA\Property,
|
||||||
ORM\Column(length: 50),
|
ORM\Column(length: 50),
|
||||||
Groups([EntityGroupsInterface::GROUP_GENERAL, EntityGroupsInterface::GROUP_ALL])
|
Groups([EntityGroupsInterface::GROUP_GENERAL, EntityGroupsInterface::GROUP_ALL])
|
||||||
]
|
]
|
||||||
|
|
|
@ -11,6 +11,7 @@ use App\Http\ServerRequest;
|
||||||
use Gettext\GettextTranslator;
|
use Gettext\GettextTranslator;
|
||||||
use Gettext\TranslatorFunctions;
|
use Gettext\TranslatorFunctions;
|
||||||
use Gettext\TranslatorInterface;
|
use Gettext\TranslatorInterface;
|
||||||
|
use Locale;
|
||||||
use PhpMyAdmin\MoTranslator\Loader;
|
use PhpMyAdmin\MoTranslator\Loader;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
|
@ -124,7 +125,7 @@ enum SupportedLocales: string
|
||||||
}
|
}
|
||||||
|
|
||||||
$server_params = $request->getServerParams();
|
$server_params = $request->getServerParams();
|
||||||
$browser_locale = \Locale::acceptFromHttp($server_params['HTTP_ACCEPT_LANGUAGE'] ?? '');
|
$browser_locale = Locale::acceptFromHttp($server_params['HTTP_ACCEPT_LANGUAGE'] ?? '');
|
||||||
|
|
||||||
if (!empty($browser_locale)) {
|
if (!empty($browser_locale)) {
|
||||||
if (2 === strlen($browser_locale)) {
|
if (2 === strlen($browser_locale)) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||||
namespace App\Event\Radio;
|
namespace App\Event\Radio;
|
||||||
|
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
|
use RuntimeException;
|
||||||
use Symfony\Contracts\EventDispatcher\Event;
|
use Symfony\Contracts\EventDispatcher\Event;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,7 +81,7 @@ class AnnotateNextSong extends Event
|
||||||
public function buildAnnotations(): string
|
public function buildAnnotations(): string
|
||||||
{
|
{
|
||||||
if (empty($this->songPath)) {
|
if (empty($this->songPath)) {
|
||||||
throw new \RuntimeException('No valid path for song.');
|
throw new RuntimeException('No valid path for song.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->annotations = array_filter($this->annotations);
|
$this->annotations = array_filter($this->annotations);
|
||||||
|
|
|
@ -19,6 +19,8 @@ use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||||
use Symfony\Component\Lock\Key;
|
use Symfony\Component\Lock\Key;
|
||||||
use Symfony\Component\Lock\PersistingStoreInterface;
|
use Symfony\Component\Lock\PersistingStoreInterface;
|
||||||
|
|
||||||
|
use const PHP_INT_MAX;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copied from Symfony 5.x as it was deprecated in 6.x with no suitable replacement.
|
* Copied from Symfony 5.x as it was deprecated in 6.x with no suitable replacement.
|
||||||
*
|
*
|
||||||
|
@ -38,7 +40,7 @@ final class RetryTillSaveStore implements BlockingStoreInterface, LoggerAwareInt
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private PersistingStoreInterface $decorated,
|
private PersistingStoreInterface $decorated,
|
||||||
private int $retrySleep = 100,
|
private int $retrySleep = 100,
|
||||||
private int $retryCount = \PHP_INT_MAX
|
private int $retryCount = PHP_INT_MAX
|
||||||
) {
|
) {
|
||||||
$this->logger = new NullLogger();
|
$this->logger = new NullLogger();
|
||||||
}
|
}
|
||||||
|
@ -68,12 +70,10 @@ final class RetryTillSaveStore implements BlockingStoreInterface, LoggerAwareInt
|
||||||
}
|
}
|
||||||
} while (++$retry < $this->retryCount);
|
} while (++$retry < $this->retryCount);
|
||||||
|
|
||||||
if (null !== $this->logger) {
|
$this->logger?->warning(
|
||||||
$this->logger->warning(
|
'Failed to store the "{resource}" lock. Abort after {retry} retry.',
|
||||||
'Failed to store the "{resource}" lock. Abort after {retry} retry.',
|
['resource' => $key, 'retry' => $retry]
|
||||||
['resource' => $key, 'retry' => $retry]
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new LockConflictedException();
|
throw new LockConflictedException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ class MimeTypeExtensionMap extends GeneratedExtensionToMimeTypeMap
|
||||||
|
|
||||||
public function lookupMimeType(string $extension): ?string
|
public function lookupMimeType(string $extension): ?string
|
||||||
{
|
{
|
||||||
return self::MIME_TYPES_FOR_EXTENSIONS[$extension]
|
return parent::lookupMimeType($extension)
|
||||||
?? self::ADDED_MIME_TYPES[$extension]
|
?? self::ADDED_MIME_TYPES[$extension]
|
||||||
?? null;
|
?? null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||||
namespace App\Message;
|
namespace App\Message;
|
||||||
|
|
||||||
use App\Entity\Api\NowPlaying\NowPlaying;
|
use App\Entity\Api\NowPlaying\NowPlaying;
|
||||||
use App\MessageQueue\QueueManager;
|
|
||||||
use App\MessageQueue\QueueManagerInterface;
|
use App\MessageQueue\QueueManagerInterface;
|
||||||
|
|
||||||
class DispatchWebhookMessage extends AbstractUniqueMessage
|
class DispatchWebhookMessage extends AbstractUniqueMessage
|
||||||
|
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||||
namespace App\Message;
|
namespace App\Message;
|
||||||
|
|
||||||
use App\Environment;
|
use App\Environment;
|
||||||
use App\MessageQueue\QueueManagerInterface;
|
|
||||||
|
|
||||||
class TestWebhookMessage extends AbstractUniqueMessage
|
class TestWebhookMessage extends AbstractUniqueMessage
|
||||||
{
|
{
|
||||||
|
@ -23,9 +22,4 @@ class TestWebhookMessage extends AbstractUniqueMessage
|
||||||
{
|
{
|
||||||
return Environment::getInstance()->getSyncLongExecutionTime();
|
return Environment::getInstance()->getSyncLongExecutionTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getQueue(): string
|
|
||||||
{
|
|
||||||
return QueueManagerInterface::QUEUE_NORMAL_PRIORITY;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||||
namespace App;
|
namespace App;
|
||||||
|
|
||||||
use App\Http\Response;
|
use App\Http\Response;
|
||||||
use App\Http\Router;
|
|
||||||
use App\Http\RouterInterface;
|
use App\Http\RouterInterface;
|
||||||
use App\Http\ServerRequest;
|
use App\Http\ServerRequest;
|
||||||
use Countable;
|
use Countable;
|
||||||
|
@ -179,23 +178,21 @@ class Paginator implements IteratorAggregate, Countable
|
||||||
}
|
}
|
||||||
|
|
||||||
$pageLinks = [];
|
$pageLinks = [];
|
||||||
if ($this->router instanceof Router) {
|
$pageLinks['first'] = (string)$this->router->fromHereWithQuery(null, [], ['page' => 1]);
|
||||||
$pageLinks['first'] = (string)$this->router->fromHereWithQuery(null, [], ['page' => 1]);
|
|
||||||
|
|
||||||
$prevPage = $this->paginator->hasPreviousPage()
|
$prevPage = $this->paginator->hasPreviousPage()
|
||||||
? $this->paginator->getPreviousPage()
|
? $this->paginator->getPreviousPage()
|
||||||
: 1;
|
: 1;
|
||||||
|
|
||||||
$pageLinks['previous'] = (string)$this->router->fromHereWithQuery(null, [], ['page' => $prevPage]);
|
$pageLinks['previous'] = (string)$this->router->fromHereWithQuery(null, [], ['page' => $prevPage]);
|
||||||
|
|
||||||
$nextPage = $this->paginator->hasNextPage()
|
$nextPage = $this->paginator->hasNextPage()
|
||||||
? $this->paginator->getNextPage()
|
? $this->paginator->getNextPage()
|
||||||
: $this->paginator->getNbPages();
|
: $this->paginator->getNbPages();
|
||||||
|
|
||||||
$pageLinks['next'] = (string)$this->router->fromHereWithQuery(null, [], ['page' => $nextPage]);
|
$pageLinks['next'] = (string)$this->router->fromHereWithQuery(null, [], ['page' => $nextPage]);
|
||||||
|
|
||||||
$pageLinks['last'] = (string)$this->router->fromHereWithQuery(null, [], ['page' => $totalPages]);
|
$pageLinks['last'] = (string)$this->router->fromHereWithQuery(null, [], ['page' => $totalPages]);
|
||||||
}
|
|
||||||
|
|
||||||
return $response->withJson(
|
return $response->withJson(
|
||||||
[
|
[
|
||||||
|
|
|
@ -15,7 +15,6 @@ use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Supervisor\Exception\Fault;
|
use Supervisor\Exception\Fault;
|
||||||
use Supervisor\Exception\SupervisorException as SupervisorLibException;
|
use Supervisor\Exception\SupervisorException as SupervisorLibException;
|
||||||
use Supervisor\Process;
|
|
||||||
use Supervisor\SupervisorInterface;
|
use Supervisor\SupervisorInterface;
|
||||||
|
|
||||||
abstract class AbstractAdapter
|
abstract class AbstractAdapter
|
||||||
|
@ -106,9 +105,7 @@ abstract class AbstractAdapter
|
||||||
$program_name = $this->getProgramName($station);
|
$program_name = $this->getProgramName($station);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$process = $this->supervisor->getProcess($program_name);
|
return $this->supervisor->getProcess($program_name)->isRunning();
|
||||||
|
|
||||||
return $process instanceof Process && $process->isRunning();
|
|
||||||
} catch (Fault\BadNameException) {
|
} catch (Fault\BadNameException) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ use App\Radio\Adapters;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use RuntimeException;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
|
||||||
class Annotations implements EventSubscriberInterface
|
class Annotations implements EventSubscriberInterface
|
||||||
|
@ -52,7 +53,7 @@ class Annotations implements EventSubscriberInterface
|
||||||
$queueRow = $this->queueRepo->getNextToSendToAutoDj($station);
|
$queueRow = $this->queueRepo->getNextToSendToAutoDj($station);
|
||||||
|
|
||||||
if (null === $queueRow) {
|
if (null === $queueRow) {
|
||||||
throw new \RuntimeException('Queue is empty for station.');
|
throw new RuntimeException('Queue is empty for station.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$event = AnnotateNextSong::fromStationQueue($queueRow, $asAutoDj);
|
$event = AnnotateNextSong::fromStationQueue($queueRow, $asAutoDj);
|
||||||
|
|
|
@ -8,6 +8,7 @@ use App\Entity;
|
||||||
use App\Event\Radio\WriteLiquidsoapConfiguration;
|
use App\Event\Radio\WriteLiquidsoapConfiguration;
|
||||||
use App\Exception;
|
use App\Exception;
|
||||||
use App\Radio\Enums\LiquidsoapQueues;
|
use App\Radio\Enums\LiquidsoapQueues;
|
||||||
|
use LogicException;
|
||||||
use Psr\Http\Message\UriInterface;
|
use Psr\Http\Message\UriInterface;
|
||||||
use Symfony\Component\Process\Process;
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
@ -329,7 +330,7 @@ class Liquidsoap extends AbstractBackend
|
||||||
$process->run();
|
$process->run();
|
||||||
|
|
||||||
if (1 === $process->getExitCode()) {
|
if (1 === $process->getExitCode()) {
|
||||||
throw new \LogicException($process->getOutput());
|
throw new LogicException($process->getOutput());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ namespace App\Radio\Backend\Liquidsoap\Command;
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
use App\Radio\Enums\BackendAdapters;
|
use App\Radio\Enums\BackendAdapters;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
|
use ReflectionClass;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
abstract class AbstractCommand
|
abstract class AbstractCommand
|
||||||
{
|
{
|
||||||
|
@ -30,7 +32,7 @@ abstract class AbstractCommand
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
$className = (new \ReflectionClass(static::class))->getShortName();
|
$className = (new ReflectionClass(static::class))->getShortName();
|
||||||
$this->logger->debug(
|
$this->logger->debug(
|
||||||
sprintf('Running Internal Command %s', $className),
|
sprintf('Running Internal Command %s', $className),
|
||||||
[
|
[
|
||||||
|
@ -54,7 +56,7 @@ abstract class AbstractCommand
|
||||||
} else {
|
} else {
|
||||||
return (string)$result;
|
return (string)$result;
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$this->logger->error(
|
$this->logger->error(
|
||||||
sprintf(
|
sprintf(
|
||||||
'Error with Internal Command %s: %s',
|
'Error with Internal Command %s: %s',
|
||||||
|
|
|
@ -6,13 +6,14 @@ namespace App\Radio\Backend\Liquidsoap\Command;
|
||||||
|
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
use App\Flysystem\StationFilesystems;
|
use App\Flysystem\StationFilesystems;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
class CopyCommand extends AbstractCommand
|
class CopyCommand extends AbstractCommand
|
||||||
{
|
{
|
||||||
protected function doRun(Entity\Station $station, bool $asAutoDj = false, array $payload = []): string
|
protected function doRun(Entity\Station $station, bool $asAutoDj = false, array $payload = []): string
|
||||||
{
|
{
|
||||||
if (empty($payload['uri'])) {
|
if (empty($payload['uri'])) {
|
||||||
throw new \RuntimeException('No URI provided.');
|
throw new RuntimeException('No URI provided.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$uri = $payload['uri'];
|
$uri = $payload['uri'];
|
||||||
|
|
|
@ -6,6 +6,7 @@ namespace App\Radio\Backend\Liquidsoap\Command;
|
||||||
|
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
class DjAuthCommand extends AbstractCommand
|
class DjAuthCommand extends AbstractCommand
|
||||||
{
|
{
|
||||||
|
@ -22,7 +23,7 @@ class DjAuthCommand extends AbstractCommand
|
||||||
array $payload = []
|
array $payload = []
|
||||||
): bool {
|
): bool {
|
||||||
if (!$station->getEnableStreamers()) {
|
if (!$station->getEnableStreamers()) {
|
||||||
throw new \RuntimeException('Attempted DJ authentication when streamers are disabled on this station.');
|
throw new RuntimeException('Attempted DJ authentication when streamers are disabled on this station.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $payload['user'] ?? '';
|
$user = $payload['user'] ?? '';
|
||||||
|
|
|
@ -8,6 +8,7 @@ use App\Entity;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
use Psr\SimpleCache\CacheInterface;
|
use Psr\SimpleCache\CacheInterface;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
class FeedbackCommand extends AbstractCommand
|
class FeedbackCommand extends AbstractCommand
|
||||||
{
|
{
|
||||||
|
@ -24,12 +25,12 @@ class FeedbackCommand extends AbstractCommand
|
||||||
{
|
{
|
||||||
// Process extra metadata sent by Liquidsoap (if it exists).
|
// Process extra metadata sent by Liquidsoap (if it exists).
|
||||||
if (empty($payload['media_id'])) {
|
if (empty($payload['media_id'])) {
|
||||||
throw new \RuntimeException('No payload provided.');
|
throw new RuntimeException('No payload provided.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$media = $this->em->find(Entity\StationMedia::class, $payload['media_id']);
|
$media = $this->em->find(Entity\StationMedia::class, $payload['media_id']);
|
||||||
if (!$media instanceof Entity\StationMedia) {
|
if (!$media instanceof Entity\StationMedia) {
|
||||||
throw new \RuntimeException('Media ID does not exist for station.');
|
throw new RuntimeException('Media ID does not exist for station.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$sq = $this->queueRepo->findRecentlyCuedSong($station, $media);
|
$sq = $this->queueRepo->findRecentlyCuedSong($station, $media);
|
||||||
|
|
|
@ -116,7 +116,7 @@ class ConfigWriter implements EventSubscriberInterface
|
||||||
$event->appendBlock(
|
$event->appendBlock(
|
||||||
<<<EOF
|
<<<EOF
|
||||||
init.daemon.set(false)
|
init.daemon.set(false)
|
||||||
init.daemon.pidfile.path.set("${pidfile}")
|
init.daemon.pidfile.path.set("{$pidfile}")
|
||||||
|
|
||||||
log.stdout.set(true)
|
log.stdout.set(true)
|
||||||
log.file.set(false)
|
log.file.set(false)
|
||||||
|
@ -125,14 +125,14 @@ class ConfigWriter implements EventSubscriberInterface
|
||||||
|
|
||||||
settings.server.socket.set(true)
|
settings.server.socket.set(true)
|
||||||
settings.server.socket.permissions.set(0o660)
|
settings.server.socket.permissions.set(0o660)
|
||||||
settings.server.socket.path.set("${socketFile}")
|
settings.server.socket.path.set("{$socketFile}")
|
||||||
|
|
||||||
settings.harbor.bind_addrs.set(["0.0.0.0"])
|
settings.harbor.bind_addrs.set(["0.0.0.0"])
|
||||||
|
|
||||||
settings.tag.encodings.set(["UTF-8","ISO-8859-1"])
|
settings.tag.encodings.set(["UTF-8","ISO-8859-1"])
|
||||||
settings.encoder.metadata.export.set(["artist","title","album","song"])
|
settings.encoder.metadata.export.set(["artist","title","album","song"])
|
||||||
|
|
||||||
setenv("TZ", "${stationTz}")
|
setenv("TZ", "{$stationTz}")
|
||||||
|
|
||||||
autodj_is_loading = ref(true)
|
autodj_is_loading = ref(true)
|
||||||
ignore(autodj_is_loading)
|
ignore(autodj_is_loading)
|
||||||
|
@ -154,8 +154,8 @@ class ConfigWriter implements EventSubscriberInterface
|
||||||
|
|
||||||
$event->appendBlock(
|
$event->appendBlock(
|
||||||
<<<EOF
|
<<<EOF
|
||||||
azuracast_api_url = "${stationApiUrl}"
|
azuracast_api_url = "{$stationApiUrl}"
|
||||||
azuracast_api_key = "${stationApiAuth}"
|
azuracast_api_key = "{$stationApiAuth}"
|
||||||
|
|
||||||
def azuracast_api_call(~timeout_ms=2000, url, payload) =
|
def azuracast_api_call(~timeout_ms=2000, url, payload) =
|
||||||
full_url = "#{azuracast_api_url}/#{url}"
|
full_url = "#{azuracast_api_url}/#{url}"
|
||||||
|
@ -189,7 +189,7 @@ class ConfigWriter implements EventSubscriberInterface
|
||||||
|
|
||||||
$event->appendBlock(
|
$event->appendBlock(
|
||||||
<<<EOF
|
<<<EOF
|
||||||
station_media_dir = "${stationMediaDir}"
|
station_media_dir = "{$stationMediaDir}"
|
||||||
def azuracast_media_protocol(~rlog=_,~maxtime=_,arg) =
|
def azuracast_media_protocol(~rlog=_,~maxtime=_,arg) =
|
||||||
["#{station_media_dir}/#{arg}"]
|
["#{station_media_dir}/#{arg}"]
|
||||||
end
|
end
|
||||||
|
@ -240,7 +240,7 @@ class ConfigWriter implements EventSubscriberInterface
|
||||||
<<<EOF
|
<<<EOF
|
||||||
# Optimize Performance
|
# Optimize Performance
|
||||||
runtime.gc.set(runtime.gc.get().{
|
runtime.gc.set(runtime.gc.get().{
|
||||||
space_overhead = ${gcSpaceOverhead},
|
space_overhead = {$gcSpaceOverhead},
|
||||||
allocation_policy = 2
|
allocation_policy = 2
|
||||||
})
|
})
|
||||||
EOF
|
EOF
|
||||||
|
@ -552,12 +552,12 @@ class ConfigWriter implements EventSubscriberInterface
|
||||||
|
|
||||||
$event->appendBlock(
|
$event->appendBlock(
|
||||||
<<< EOF
|
<<< EOF
|
||||||
requests = request.queue(id="${requestsQueueName}")
|
requests = request.queue(id="{$requestsQueueName}")
|
||||||
requests = cue_cut(id="cue_${requestsQueueName}", requests)
|
requests = cue_cut(id="cue_{$requestsQueueName}", requests)
|
||||||
radio = fallback(id="requests_fallback", track_sensitive = true, [requests, radio])
|
radio = fallback(id="requests_fallback", track_sensitive = true, [requests, radio])
|
||||||
|
|
||||||
interrupting_queue = request.queue(id="${interruptingQueueName}")
|
interrupting_queue = request.queue(id="{$interruptingQueueName}")
|
||||||
interrupting_queue = cue_cut(id="cue_${interruptingQueueName}", interrupting_queue)
|
interrupting_queue = cue_cut(id="cue_{$interruptingQueueName}", interrupting_queue)
|
||||||
radio = fallback(id="interrupting_fallback", track_sensitive = false, [interrupting_queue, radio])
|
radio = fallback(id="interrupting_fallback", track_sensitive = false, [interrupting_queue, radio])
|
||||||
|
|
||||||
add_skip_command(radio)
|
add_skip_command(radio)
|
||||||
|
@ -800,7 +800,7 @@ class ConfigWriter implements EventSubscriberInterface
|
||||||
ignore(radio_without_live)
|
ignore(radio_without_live)
|
||||||
|
|
||||||
# Live Broadcasting
|
# Live Broadcasting
|
||||||
live = input.harbor(${harborParams})
|
live = input.harbor({$harborParams})
|
||||||
|
|
||||||
def insert_missing(m) =
|
def insert_missing(m) =
|
||||||
if m == [] then
|
if m == [] then
|
||||||
|
@ -827,14 +827,14 @@ class ConfigWriter implements EventSubscriberInterface
|
||||||
$event->appendBlock(
|
$event->appendBlock(
|
||||||
<<< EOF
|
<<< EOF
|
||||||
# Record Live Broadcasts
|
# Record Live Broadcasts
|
||||||
recording_base_path = "${recordBasePath}"
|
recording_base_path = "{$recordBasePath}"
|
||||||
recording_extension = "${recordExtension}"
|
recording_extension = "{$recordExtension}"
|
||||||
|
|
||||||
output.file(
|
output.file(
|
||||||
{$formatString},
|
{$formatString},
|
||||||
fun () -> begin
|
fun () -> begin
|
||||||
if (!live_enabled) then
|
if (!live_enabled) then
|
||||||
"#{recording_base_path}/#{!live_dj}/${recordPathPrefix}_%Y%m%d-%H%M%S.#{recording_extension}.tmp"
|
"#{recording_base_path}/#{!live_dj}/{$recordPathPrefix}_%Y%m%d-%H%M%S.#{recording_extension}.tmp"
|
||||||
else
|
else
|
||||||
""
|
""
|
||||||
end
|
end
|
||||||
|
@ -897,7 +897,7 @@ class ConfigWriter implements EventSubscriberInterface
|
||||||
|
|
||||||
$event->appendBlock(
|
$event->appendBlock(
|
||||||
<<<EOF
|
<<<EOF
|
||||||
radio = fallback(id="safe_fallback", track_sensitive = false, [radio, single(id="error_jingle", "${errorFile}")])
|
radio = fallback(id="safe_fallback", track_sensitive = false, [radio, single(id="error_jingle", "{$errorFile}")])
|
||||||
EOF
|
EOF
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class PlaylistFileWriter implements EventSubscriberInterface
|
class PlaylistFileWriter implements EventSubscriberInterface
|
||||||
{
|
{
|
||||||
|
@ -136,7 +137,7 @@ class PlaylistFileWriter implements EventSubscriberInterface
|
||||||
try {
|
try {
|
||||||
$this->eventDispatcher->dispatch($event);
|
$this->eventDispatcher->dispatch($event);
|
||||||
$playlistFile[] = $event->buildAnnotations();
|
$playlistFile[] = $event->buildAnnotations();
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ use App\Radio\Enums\BackendAdapters;
|
||||||
use App\Radio\Enums\FrontendAdapters;
|
use App\Radio\Enums\FrontendAdapters;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
|
use RuntimeException;
|
||||||
use Supervisor\Exception\SupervisorException;
|
use Supervisor\Exception\SupervisorException;
|
||||||
use Supervisor\SupervisorInterface;
|
use Supervisor\SupervisorInterface;
|
||||||
|
|
||||||
|
@ -104,7 +105,7 @@ class Configuration
|
||||||
|
|
||||||
if (!$station->getIsEnabled()) {
|
if (!$station->getIsEnabled()) {
|
||||||
$this->unlinkAndStopStation($station, $reloadSupervisor);
|
$this->unlinkAndStopStation($station, $reloadSupervisor);
|
||||||
throw new \RuntimeException('Station is disabled.');
|
throw new RuntimeException('Station is disabled.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$frontend = $this->adapters->getFrontendAdapter($station);
|
$frontend = $this->adapters->getFrontendAdapter($station);
|
||||||
|
@ -113,7 +114,7 @@ class Configuration
|
||||||
// If no processes need to be managed, remove any existing config.
|
// If no processes need to be managed, remove any existing config.
|
||||||
if (!$frontend->hasCommand($station) && !$backend->hasCommand($station)) {
|
if (!$frontend->hasCommand($station) && !$backend->hasCommand($station)) {
|
||||||
$this->unlinkAndStopStation($station, $reloadSupervisor);
|
$this->unlinkAndStopStation($station, $reloadSupervisor);
|
||||||
throw new \RuntimeException('Station has no local services.');
|
throw new RuntimeException('Station has no local services.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// If using AutoDJ and there is no media, don't spin up services.
|
// If using AutoDJ and there is no media, don't spin up services.
|
||||||
|
@ -122,7 +123,7 @@ class Configuration
|
||||||
&& !$this->stationPlaylistRepo->stationHasActivePlaylists($station)
|
&& !$this->stationPlaylistRepo->stationHasActivePlaylists($station)
|
||||||
) {
|
) {
|
||||||
$this->unlinkAndStopStation($station, $reloadSupervisor);
|
$this->unlinkAndStopStation($station, $reloadSupervisor);
|
||||||
throw new \RuntimeException('Station has no media assigned to playlists.');
|
throw new RuntimeException('Station has no media assigned to playlists.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get group information
|
// Get group information
|
||||||
|
@ -174,8 +175,8 @@ class Configuration
|
||||||
$backend->reload($station);
|
$backend->reload($station);
|
||||||
$frontend->reload($station);
|
$frontend->reload($station);
|
||||||
} else {
|
} else {
|
||||||
$this->supervisor->stopProcessGroup($backend_group, true);
|
$this->supervisor->stopProcessGroup($backend_group);
|
||||||
$this->supervisor->startProcessGroup($backend_group, true);
|
$this->supervisor->startProcessGroup($backend_group);
|
||||||
}
|
}
|
||||||
} catch (SupervisorException) {
|
} catch (SupervisorException) {
|
||||||
}
|
}
|
||||||
|
@ -483,7 +484,7 @@ class Configuration
|
||||||
|
|
||||||
// Try forcing the group to stop, but don't hard-fail if it doesn't.
|
// Try forcing the group to stop, but don't hard-fail if it doesn't.
|
||||||
try {
|
try {
|
||||||
$this->supervisor->stopProcessGroup($station_group, true);
|
$this->supervisor->stopProcessGroup($station_group);
|
||||||
$this->supervisor->removeProcessGroup($station_group);
|
$this->supervisor->removeProcessGroup($station_group);
|
||||||
} catch (SupervisorException) {
|
} catch (SupervisorException) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ namespace App\Radio\Frontend\Blocklist;
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
use App\Radio\Enums\FrontendAdapters;
|
use App\Radio\Enums\FrontendAdapters;
|
||||||
use App\Service\IpGeolocation;
|
use App\Service\IpGeolocation;
|
||||||
|
use InvalidArgumentException;
|
||||||
use PhpIP\IP;
|
use PhpIP\IP;
|
||||||
use PhpIP\IPBlock;
|
use PhpIP\IPBlock;
|
||||||
|
|
||||||
|
@ -84,7 +85,7 @@ class BlocklistParser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\InvalidArgumentException) {
|
} catch (InvalidArgumentException) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ class RateLimit
|
||||||
$rateLimiterFactory = new RateLimiterFactory($config, $cacheStore, $this->lockFactory);
|
$rateLimiterFactory = new RateLimiterFactory($config, $cacheStore, $this->lockFactory);
|
||||||
$rateLimiter = $rateLimiterFactory->create($key);
|
$rateLimiter = $rateLimiterFactory->create($key);
|
||||||
|
|
||||||
if (false === $rateLimiter->consume(1)->isAccepted()) {
|
if (false === $rateLimiter->consume()->isAccepted()) {
|
||||||
throw new Exception\RateLimitExceededException();
|
throw new Exception\RateLimitExceededException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,13 +65,13 @@ class Flow
|
||||||
return self::handleStandardUpload($request, $tempDir);
|
return self::handleStandardUpload($request, $tempDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
$flowIdentifier = $params['flowIdentifier'] ?? '';
|
$flowIdentifier = $params['flowIdentifier'];
|
||||||
$flowChunkNumber = (int)($params['flowChunkNumber'] ?? 1);
|
$flowChunkNumber = (int)($params['flowChunkNumber'] ?? 1);
|
||||||
|
|
||||||
$targetSize = (int)($params['flowTotalSize'] ?? 0);
|
$targetSize = (int)($params['flowTotalSize'] ?? 0);
|
||||||
$targetChunks = (int)($params['flowTotalChunks'] ?? 1);
|
$targetChunks = (int)($params['flowTotalChunks']);
|
||||||
|
|
||||||
$flowFilename = $params['flowFilename'] ?? ($flowIdentifier ?: ('upload-' . date('Ymd')));
|
$flowFilename = $params['flowFilename'] ?? ($flowIdentifier);
|
||||||
|
|
||||||
// init the destination file (format <filename.ext>.part<#chunk>
|
// init the destination file (format <filename.ext>.part<#chunk>
|
||||||
$chunkBaseDir = $tempDir . '/' . $flowIdentifier;
|
$chunkBaseDir = $tempDir . '/' . $flowIdentifier;
|
||||||
|
|
|
@ -8,6 +8,7 @@ use App\Service\IpGeolocator;
|
||||||
use Exception;
|
use Exception;
|
||||||
use MaxMind\Db\Reader;
|
use MaxMind\Db\Reader;
|
||||||
use Psr\Cache\CacheItemPoolInterface;
|
use Psr\Cache\CacheItemPoolInterface;
|
||||||
|
use RuntimeException;
|
||||||
use Symfony\Component\Cache\Adapter\ProxyAdapter;
|
use Symfony\Component\Cache\Adapter\ProxyAdapter;
|
||||||
use Symfony\Component\Cache\CacheItem;
|
use Symfony\Component\Cache\CacheItem;
|
||||||
use Symfony\Contracts\Cache\CacheInterface;
|
use Symfony\Contracts\Cache\CacheInterface;
|
||||||
|
@ -76,7 +77,7 @@ class IpGeolocation
|
||||||
|
|
||||||
$reader = $this->reader;
|
$reader = $this->reader;
|
||||||
if (null === $reader) {
|
if (null === $reader) {
|
||||||
throw new \RuntimeException('No IP Geolocation reader available.');
|
throw new RuntimeException('No IP Geolocation reader available.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$cacheKey = $this->readerShortName . '_' . str_replace([':', '.'], '_', $ip);
|
$cacheKey = $this->readerShortName . '_' . str_replace([':', '.'], '_', $ip);
|
||||||
|
|
|
@ -74,7 +74,7 @@ class CheckMediaTask extends AbstractTask
|
||||||
$this->logger->info(
|
$this->logger->info(
|
||||||
sprintf(
|
sprintf(
|
||||||
'Processing media for storage location %s...',
|
'Processing media for storage location %s...',
|
||||||
(string)$storageLocation
|
$storageLocation
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ class CheckMediaTask extends AbstractTask
|
||||||
);
|
);
|
||||||
} catch (FilesystemException $e) {
|
} catch (FilesystemException $e) {
|
||||||
$this->logger->error(
|
$this->logger->error(
|
||||||
sprintf('Flysystem Error for Storage Space %s', (string)$storageLocation),
|
sprintf('Flysystem Error for Storage Space %s', $storageLocation),
|
||||||
[
|
[
|
||||||
'exception' => $e,
|
'exception' => $e,
|
||||||
]
|
]
|
||||||
|
@ -171,7 +171,7 @@ class CheckMediaTask extends AbstractTask
|
||||||
|
|
||||||
$this->processNewFiles($storageLocation, $queuedNewFiles, $musicFiles, $stats);
|
$this->processNewFiles($storageLocation, $queuedNewFiles, $musicFiles, $stats);
|
||||||
|
|
||||||
$this->logger->debug(sprintf('Media processed for "%s".', (string)$storageLocation), $stats);
|
$this->logger->debug(sprintf('Media processed for "%s".', $storageLocation), $stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function processExistingMediaRows(
|
protected function processExistingMediaRows(
|
||||||
|
@ -221,7 +221,7 @@ class CheckMediaTask extends AbstractTask
|
||||||
} else {
|
} else {
|
||||||
$media = $this->em->find(Entity\StationMedia::class, $mediaRow['id']);
|
$media = $this->em->find(Entity\StationMedia::class, $mediaRow['id']);
|
||||||
if ($media instanceof Entity\StationMedia) {
|
if ($media instanceof Entity\StationMedia) {
|
||||||
$this->mediaRepo->remove($media, false);
|
$this->mediaRepo->remove($media);
|
||||||
}
|
}
|
||||||
|
|
||||||
$stats['deleted']++;
|
$stats['deleted']++;
|
||||||
|
|
|
@ -8,6 +8,7 @@ use App\Entity;
|
||||||
use Exception;
|
use Exception;
|
||||||
use League\Flysystem\StorageAttributes;
|
use League\Flysystem\StorageAttributes;
|
||||||
use Symfony\Component\Finder\Finder;
|
use Symfony\Component\Finder\Finder;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class CleanupStorageTask extends AbstractTask
|
class CleanupStorageTask extends AbstractTask
|
||||||
{
|
{
|
||||||
|
@ -22,7 +23,7 @@ class CleanupStorageTask extends AbstractTask
|
||||||
try {
|
try {
|
||||||
/** @var Entity\Station $station */
|
/** @var Entity\Station $station */
|
||||||
$this->cleanStationTempFiles($station);
|
$this->cleanStationTempFiles($station);
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$this->logger->error($e->getMessage(), [
|
$this->logger->error($e->getMessage(), [
|
||||||
'station' => (string)$station,
|
'station' => (string)$station,
|
||||||
]);
|
]);
|
||||||
|
@ -34,7 +35,7 @@ class CleanupStorageTask extends AbstractTask
|
||||||
try {
|
try {
|
||||||
/** @var Entity\StorageLocation $storageLocation */
|
/** @var Entity\StorageLocation $storageLocation */
|
||||||
$this->cleanMediaStorageLocation($storageLocation);
|
$this->cleanMediaStorageLocation($storageLocation);
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$this->logger->error($e->getMessage(), [
|
$this->logger->error($e->getMessage(), [
|
||||||
'storageLocation' => (string)$storageLocation,
|
'storageLocation' => (string)$storageLocation,
|
||||||
]);
|
]);
|
||||||
|
@ -80,7 +81,7 @@ class CleanupStorageTask extends AbstractTask
|
||||||
|
|
||||||
if (0 === count($allUniqueIds)) {
|
if (0 === count($allUniqueIds)) {
|
||||||
$this->logger->notice(
|
$this->logger->notice(
|
||||||
sprintf('Skipping storage location %s: no media found.', (string)$storageLocation)
|
sprintf('Skipping storage location %s: no media found.', $storageLocation)
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ use App\Doctrine\ReloadableEntityManagerInterface;
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Finder\Finder;
|
use Symfony\Component\Finder\Finder;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class MoveBroadcastsTask extends AbstractTask
|
class MoveBroadcastsTask extends AbstractTask
|
||||||
{
|
{
|
||||||
|
@ -35,7 +36,7 @@ class MoveBroadcastsTask extends AbstractTask
|
||||||
try {
|
try {
|
||||||
/** @var Entity\StorageLocation $storageLocation */
|
/** @var Entity\StorageLocation $storageLocation */
|
||||||
$this->processForStorageLocation($storageLocation);
|
$this->processForStorageLocation($storageLocation);
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$this->logger->error($e->getMessage(), [
|
$this->logger->error($e->getMessage(), [
|
||||||
'storageLocation' => (string)$storageLocation,
|
'storageLocation' => (string)$storageLocation,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -12,6 +12,7 @@ use League\Flysystem\StorageAttributes;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Supervisor\SupervisorInterface;
|
use Supervisor\SupervisorInterface;
|
||||||
use Symfony\Component\Finder\Finder;
|
use Symfony\Component\Finder\Finder;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class RotateLogsTask extends AbstractTask
|
class RotateLogsTask extends AbstractTask
|
||||||
{
|
{
|
||||||
|
@ -43,7 +44,7 @@ class RotateLogsTask extends AbstractTask
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->rotateStationLogs($station);
|
$this->rotateStationLogs($station);
|
||||||
} catch (\Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$this->logger->error($e->getMessage(), [
|
$this->logger->error($e->getMessage(), [
|
||||||
'station' => (string)$station,
|
'station' => (string)$station,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -8,6 +8,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Tests;
|
namespace App\Tests;
|
||||||
|
|
||||||
|
use App\AppFactory;
|
||||||
use App\Doctrine\ReloadableEntityManagerInterface;
|
use App\Doctrine\ReloadableEntityManagerInterface;
|
||||||
use App\Enums\ApplicationEnvironment;
|
use App\Enums\ApplicationEnvironment;
|
||||||
use App\Environment;
|
use App\Environment;
|
||||||
|
@ -38,16 +39,10 @@ class Module extends Framework implements DoctrineProvider
|
||||||
|
|
||||||
public function _initialize(): void
|
public function _initialize(): void
|
||||||
{
|
{
|
||||||
/** @var string $container_class The fully qualified name of the container class. */
|
$this->app = AppFactory::createApp(
|
||||||
$container_class = $this->config['container'];
|
|
||||||
|
|
||||||
$autoloader = $GLOBALS['autoloader'];
|
|
||||||
|
|
||||||
$this->app = $container_class::createApp(
|
|
||||||
$autoloader,
|
|
||||||
[
|
[
|
||||||
Environment::BASE_DIR => Configuration::projectDir(),
|
Environment::BASE_DIR => Configuration::projectDir(),
|
||||||
Environment::APP_ENV => ApplicationEnvironment::Testing->value,
|
Environment::APP_ENV => ApplicationEnvironment::Testing->value,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,11 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Xml;
|
namespace App\Xml;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
use XMLReader;
|
use XMLReader;
|
||||||
|
|
||||||
|
use const LIBXML_XINCLUDE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XML config reader.
|
* XML config reader.
|
||||||
*/
|
*/
|
||||||
|
@ -32,14 +35,14 @@ class Reader
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var XMLReader|false $reader */
|
/** @var XMLReader|false $reader */
|
||||||
$reader = XMLReader::XML($string, null, \LIBXML_XINCLUDE);
|
$reader = XMLReader::XML($string, null, LIBXML_XINCLUDE);
|
||||||
if (false === $reader) {
|
if (false === $reader) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
set_error_handler(
|
set_error_handler(
|
||||||
function ($error, $message = '') {
|
function ($error, $message = '') {
|
||||||
throw new \RuntimeException(
|
throw new RuntimeException(
|
||||||
sprintf('Error reading XML string: %s', $message),
|
sprintf('Error reading XML string: %s', $message),
|
||||||
$error
|
$error
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,6 +8,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Xml;
|
namespace App\Xml;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
use XMLWriter;
|
use XMLWriter;
|
||||||
|
|
||||||
class Writer
|
class Writer
|
||||||
|
@ -71,7 +72,7 @@ class Writer
|
||||||
$branchType = 'string';
|
$branchType = 'string';
|
||||||
}
|
}
|
||||||
} elseif ($branchType !== (is_numeric($key) ? 'numeric' : 'string')) {
|
} elseif ($branchType !== (is_numeric($key) ? 'numeric' : 'string')) {
|
||||||
throw new \RuntimeException('Mixing of string and numeric keys is not allowed');
|
throw new RuntimeException('Mixing of string and numeric keys is not allowed');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($branchType === 'numeric') {
|
if ($branchType === 'numeric') {
|
||||||
|
|
|
@ -2,8 +2,7 @@ actor: FunctionalTester
|
||||||
suite_namespace: \Functional
|
suite_namespace: \Functional
|
||||||
modules:
|
modules:
|
||||||
enabled:
|
enabled:
|
||||||
- \App\Tests\Module:
|
- \App\Tests\Module
|
||||||
container: \App\AppFactory
|
|
||||||
- Doctrine2:
|
- Doctrine2:
|
||||||
depends: \App\Tests\Module
|
depends: \App\Tests\Module
|
||||||
- REST:
|
- REST:
|
||||||
|
|
|
@ -2,7 +2,6 @@ actor: UnitTester
|
||||||
suite_namespace: \Unit
|
suite_namespace: \Unit
|
||||||
modules:
|
modules:
|
||||||
enabled:
|
enabled:
|
||||||
- \App\Tests\Module:
|
- \App\Tests\Module
|
||||||
container: \App\AppFactory
|
|
||||||
|
|
||||||
error_level: "E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED"
|
error_level: "E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED"
|
||||||
|
|
|
@ -5,12 +5,8 @@ $autoloader->addClassMap([
|
||||||
'Functional\CestAbstract' => __DIR__ . '/Functional/CestAbstract.php',
|
'Functional\CestAbstract' => __DIR__ . '/Functional/CestAbstract.php',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
\Doctrine\Common\Annotations\AnnotationRegistry::registerLoader([$autoloader, 'loadClass']);
|
|
||||||
|
|
||||||
$GLOBALS['autoloader'] = $autoloader;
|
|
||||||
|
|
||||||
if (!function_exists('__')) {
|
if (!function_exists('__')) {
|
||||||
\PhpMyAdmin\MoTranslator\Loader::loadFunctions();
|
PhpMyAdmin\MoTranslator\Loader::loadFunctions();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear output directory
|
// Clear output directory
|
||||||
|
|
|
@ -6,14 +6,13 @@
|
||||||
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);
|
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);
|
||||||
ini_set('display_errors', 1);
|
ini_set('display_errors', 1);
|
||||||
|
|
||||||
$autoloader = require dirname(__DIR__) . '/vendor/autoload.php';
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
const AZURACAST_VERSION = App\Version::FALLBACK_VERSION;
|
const AZURACAST_VERSION = App\Version::FALLBACK_VERSION;
|
||||||
const AZURACAST_API_URL = 'https://localhost/api';
|
const AZURACAST_API_URL = 'https://localhost/api';
|
||||||
const AZURACAST_API_NAME = 'Testing API';
|
const AZURACAST_API_NAME = 'Testing API';
|
||||||
|
|
||||||
App\AppFactory::createCli(
|
App\AppFactory::createCli(
|
||||||
$autoloader,
|
|
||||||
[
|
[
|
||||||
App\Environment::BASE_DIR => dirname(__DIR__),
|
App\Environment::BASE_DIR => dirname(__DIR__),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,9 +6,9 @@ use App\Environment;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Psr\Log\LogLevel;
|
use Psr\Log\LogLevel;
|
||||||
|
|
||||||
$autoloader = require dirname(__DIR__) . '/vendor/autoload.php';
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
$di = App\AppFactory::buildContainer($autoloader,
|
$di = App\AppFactory::buildContainer(
|
||||||
[
|
[
|
||||||
App\Environment::BASE_DIR => dirname(__DIR__),
|
App\Environment::BASE_DIR => dirname(__DIR__),
|
||||||
App\Environment::LOG_LEVEL => LogLevel::DEBUG,
|
App\Environment::LOG_LEVEL => LogLevel::DEBUG,
|
||||||
|
|
|
@ -5,10 +5,9 @@ declare(strict_types=1);
|
||||||
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);
|
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);
|
||||||
ini_set('display_errors', '1');
|
ini_set('display_errors', '1');
|
||||||
|
|
||||||
$autoloader = require dirname(__DIR__) . '/vendor/autoload.php';
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
$app = App\AppFactory::createApp(
|
$app = App\AppFactory::createApp(
|
||||||
$autoloader,
|
|
||||||
[
|
[
|
||||||
App\Environment::BASE_DIR => dirname(__DIR__),
|
App\Environment::BASE_DIR => dirname(__DIR__),
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue