2016-05-06 10:57:34 +02:00
|
|
|
<?php
|
2020-10-15 00:19:31 +02:00
|
|
|
|
2021-07-19 07:53:45 +02:00
|
|
|
declare(strict_types=1);
|
|
|
|
|
2018-08-05 00:05:14 +02:00
|
|
|
namespace App\Radio;
|
2016-05-06 10:57:34 +02:00
|
|
|
|
2019-08-07 06:33:55 +02:00
|
|
|
use App\Entity;
|
2020-12-03 05:18:06 +01:00
|
|
|
use App\Environment;
|
2019-09-10 18:40:31 +02:00
|
|
|
use App\Exception\Supervisor\AlreadyRunningException;
|
|
|
|
use App\Exception\Supervisor\BadNameException;
|
|
|
|
use App\Exception\Supervisor\NotRunningException;
|
2019-09-20 18:44:38 +02:00
|
|
|
use App\Exception\SupervisorException;
|
2022-06-04 05:39:02 +02:00
|
|
|
use App\Http\Router;
|
2020-06-26 22:22:53 +02:00
|
|
|
use Doctrine\ORM\EntityManagerInterface;
|
2021-08-05 04:24:27 +02:00
|
|
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
2020-12-20 03:17:49 +01:00
|
|
|
use Psr\Log\LoggerInterface;
|
2020-07-02 02:26:26 +02:00
|
|
|
use Supervisor\Exception\Fault;
|
2020-07-02 03:03:28 +02:00
|
|
|
use Supervisor\Exception\SupervisorException as SupervisorLibException;
|
2022-04-11 05:57:23 +02:00
|
|
|
use Supervisor\SupervisorInterface;
|
2016-05-07 11:13:17 +02:00
|
|
|
|
2022-06-22 03:48:32 +02:00
|
|
|
abstract class AbstractLocalAdapter
|
2016-05-06 10:57:34 +02:00
|
|
|
{
|
2019-08-11 06:17:06 +02:00
|
|
|
public function __construct(
|
2021-04-23 07:24:12 +02:00
|
|
|
protected Environment $environment,
|
|
|
|
protected EntityManagerInterface $em,
|
2022-04-11 05:57:23 +02:00
|
|
|
protected SupervisorInterface $supervisor,
|
2021-08-05 04:24:27 +02:00
|
|
|
protected EventDispatcherInterface $dispatcher,
|
2022-06-04 05:39:02 +02:00
|
|
|
protected LoggerInterface $logger,
|
|
|
|
protected Router $router,
|
2019-08-11 06:17:06 +02:00
|
|
|
) {
|
2016-05-07 11:13:17 +02:00
|
|
|
}
|
|
|
|
|
2016-05-06 10:57:34 +02:00
|
|
|
/**
|
2021-01-19 18:52:45 +01:00
|
|
|
* Write configuration from Station object to the external service.
|
|
|
|
*
|
|
|
|
* @param Entity\Station $station
|
|
|
|
*
|
|
|
|
* @return bool Whether the newly written configuration differs from what was already on disk.
|
2016-05-06 10:57:34 +02:00
|
|
|
*/
|
2021-01-19 18:52:45 +01:00
|
|
|
public function write(Entity\Station $station): bool
|
|
|
|
{
|
|
|
|
$configPath = $this->getConfigurationPath($station);
|
|
|
|
if (null === $configPath) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-06-08 08:40:49 +02:00
|
|
|
$currentConfig = (is_file($configPath))
|
2021-01-19 18:52:45 +01:00
|
|
|
? file_get_contents($configPath)
|
|
|
|
: null;
|
|
|
|
|
|
|
|
$newConfig = $this->getCurrentConfiguration($station);
|
|
|
|
|
2021-01-19 22:16:32 +01:00
|
|
|
file_put_contents($configPath, $newConfig);
|
2021-01-19 18:52:45 +01:00
|
|
|
|
2021-07-19 07:53:45 +02:00
|
|
|
return 0 !== strcmp($currentConfig ?: '', $newConfig ?: '');
|
2021-01-19 18:52:45 +01:00
|
|
|
}
|
|
|
|
|
2021-01-19 22:16:32 +01:00
|
|
|
/**
|
|
|
|
* Generate the configuration for this adapter as it would exist with current database settings.
|
|
|
|
*
|
|
|
|
* @param Entity\Station $station
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function getCurrentConfiguration(Entity\Station $station): ?string
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-01-19 18:52:45 +01:00
|
|
|
/**
|
|
|
|
* Returns the main path where configuration data is stored for this adapter.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function getConfigurationPath(Entity\Station $station): ?string
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Indicate if the adapter in question is installed on the server.
|
2017-01-24 01:17:50 +01:00
|
|
|
*/
|
2021-01-19 18:52:45 +01:00
|
|
|
public function isInstalled(): bool
|
|
|
|
{
|
|
|
|
return (null !== $this->getBinary());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the binary executable location for this item.
|
|
|
|
*
|
|
|
|
* @return string|null Returns either the path to the binary if it exists or null for no binary.
|
|
|
|
*/
|
|
|
|
public function getBinary(): ?string
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
2017-01-24 01:17:50 +01:00
|
|
|
|
2016-12-15 02:54:08 +01:00
|
|
|
/**
|
|
|
|
* Check if the service is running.
|
|
|
|
*
|
2018-09-22 13:52:43 +02:00
|
|
|
* @param Entity\Station $station
|
2016-12-15 02:54:08 +01:00
|
|
|
*/
|
2018-09-22 13:52:43 +02:00
|
|
|
public function isRunning(Entity\Station $station): bool
|
2016-12-13 00:01:28 +01:00
|
|
|
{
|
2021-01-19 18:52:45 +01:00
|
|
|
if (!$this->hasCommand($station)) {
|
|
|
|
return true;
|
2016-10-14 02:47:17 +02:00
|
|
|
}
|
2016-12-15 02:54:08 +01:00
|
|
|
|
2021-01-19 18:52:45 +01:00
|
|
|
$program_name = $this->getProgramName($station);
|
|
|
|
|
2021-01-20 08:54:03 +01:00
|
|
|
try {
|
2022-05-08 20:05:02 +02:00
|
|
|
return $this->supervisor->getProcess($program_name)->isRunning();
|
2021-06-08 08:40:49 +02:00
|
|
|
} catch (Fault\BadNameException) {
|
2021-01-20 08:54:03 +01:00
|
|
|
return false;
|
|
|
|
}
|
2016-10-14 02:47:17 +02:00
|
|
|
}
|
2018-08-19 12:40:05 +02:00
|
|
|
|
2016-09-29 03:36:22 +02:00
|
|
|
/**
|
2019-09-04 20:00:51 +02:00
|
|
|
* Return a boolean indicating whether the adapter has an executable command associated with it.
|
2018-01-12 12:05:20 +01:00
|
|
|
*
|
2018-09-22 13:52:43 +02:00
|
|
|
* @param Entity\Station $station
|
2016-09-29 03:36:22 +02:00
|
|
|
*/
|
2019-09-04 20:00:51 +02:00
|
|
|
public function hasCommand(Entity\Station $station): bool
|
2016-12-13 00:01:28 +01:00
|
|
|
{
|
2021-11-07 06:02:44 +01:00
|
|
|
if ($this->environment->isTesting() || !$station->getIsEnabled()) {
|
2019-09-04 20:00:51 +02:00
|
|
|
return false;
|
2017-01-24 01:17:50 +01:00
|
|
|
}
|
2019-09-04 20:00:51 +02:00
|
|
|
|
|
|
|
return ($this->getCommand($station) !== null);
|
2016-12-13 00:01:28 +01:00
|
|
|
}
|
2016-08-31 07:09:08 +02:00
|
|
|
|
2016-10-14 02:47:17 +02:00
|
|
|
/**
|
2019-09-04 20:00:51 +02:00
|
|
|
* Return the shell command required to run the program.
|
2018-01-12 12:05:20 +01:00
|
|
|
*
|
2018-09-22 13:52:43 +02:00
|
|
|
* @param Entity\Station $station
|
2016-10-14 02:47:17 +02:00
|
|
|
*/
|
2020-10-05 08:44:26 +02:00
|
|
|
public function getCommand(Entity\Station $station): ?string
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
2017-01-22 21:58:40 +01:00
|
|
|
|
2019-09-04 20:00:51 +02:00
|
|
|
/**
|
|
|
|
* Return the program's fully qualified supervisord name.
|
|
|
|
*
|
|
|
|
* @param Entity\Station $station
|
|
|
|
*/
|
|
|
|
abstract public function getProgramName(Entity\Station $station): string;
|
|
|
|
|
2021-01-19 18:52:45 +01:00
|
|
|
/**
|
|
|
|
* Restart the executable service.
|
2019-09-20 18:44:38 +02:00
|
|
|
*
|
2021-01-19 18:52:45 +01:00
|
|
|
* @param Entity\Station $station
|
2016-12-13 00:01:28 +01:00
|
|
|
*/
|
2018-09-22 13:52:43 +02:00
|
|
|
public function restart(Entity\Station $station): void
|
2016-12-13 00:01:28 +01:00
|
|
|
{
|
2018-09-22 13:52:43 +02:00
|
|
|
$this->stop($station);
|
|
|
|
$this->start($station);
|
2016-10-14 02:47:17 +02:00
|
|
|
}
|
|
|
|
|
2021-11-16 14:13:43 +01:00
|
|
|
/**
|
|
|
|
* Execute a non-destructive reload if the adapter supports it.
|
|
|
|
*
|
|
|
|
* @param Entity\Station $station
|
|
|
|
*/
|
|
|
|
public function reload(Entity\Station $station): void
|
|
|
|
{
|
|
|
|
$this->restart($station);
|
|
|
|
}
|
|
|
|
|
2016-12-13 00:01:28 +01:00
|
|
|
/**
|
2019-09-04 20:00:51 +02:00
|
|
|
* Stop the executable service.
|
2019-03-08 03:01:51 +01:00
|
|
|
*
|
|
|
|
* @param Entity\Station $station
|
2019-09-20 18:44:38 +02:00
|
|
|
*
|
2019-12-07 01:57:50 +01:00
|
|
|
* @throws SupervisorException
|
2019-09-10 18:40:31 +02:00
|
|
|
* @throws NotRunningException
|
2019-03-08 03:01:51 +01:00
|
|
|
*/
|
2019-09-04 20:00:51 +02:00
|
|
|
public function stop(Entity\Station $station): void
|
|
|
|
{
|
|
|
|
if ($this->hasCommand($station)) {
|
|
|
|
$program_name = $this->getProgramName($station);
|
2019-03-08 03:01:51 +01:00
|
|
|
|
2019-09-04 20:00:51 +02:00
|
|
|
try {
|
|
|
|
$this->supervisor->stopProcess($program_name);
|
2020-12-20 03:17:49 +01:00
|
|
|
$this->logger->info(
|
2020-10-15 00:19:31 +02:00
|
|
|
'Adapter "' . static::class . '" stopped.',
|
|
|
|
['station_id' => $station->getId(), 'station_name' => $station->getName()]
|
|
|
|
);
|
2020-07-02 03:03:28 +02:00
|
|
|
} catch (SupervisorLibException $e) {
|
2020-07-03 22:24:04 +02:00
|
|
|
$this->handleSupervisorException($e, $program_name, $station);
|
2020-07-02 03:03:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Start the executable service.
|
|
|
|
*
|
|
|
|
* @param Entity\Station $station
|
|
|
|
*
|
|
|
|
* @throws SupervisorException
|
|
|
|
* @throws AlreadyRunningException
|
|
|
|
*/
|
|
|
|
public function start(Entity\Station $station): void
|
|
|
|
{
|
|
|
|
if ($this->hasCommand($station)) {
|
|
|
|
$program_name = $this->getProgramName($station);
|
|
|
|
|
|
|
|
try {
|
|
|
|
$this->supervisor->startProcess($program_name);
|
2020-12-20 03:17:49 +01:00
|
|
|
$this->logger->info(
|
2020-10-15 00:19:31 +02:00
|
|
|
'Adapter "' . static::class . '" started.',
|
|
|
|
['station_id' => $station->getId(), 'station_name' => $station->getName()]
|
|
|
|
);
|
2020-07-02 03:03:28 +02:00
|
|
|
} catch (SupervisorLibException $e) {
|
2020-07-03 22:24:04 +02:00
|
|
|
$this->handleSupervisorException($e, $program_name, $station);
|
2019-09-04 20:00:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-03-08 03:01:51 +01:00
|
|
|
|
2018-06-09 18:17:21 +02:00
|
|
|
/**
|
|
|
|
* Internal handling of any Supervisor-related exception, to add richer data to it.
|
|
|
|
*
|
2020-07-02 03:03:28 +02:00
|
|
|
* @param SupervisorLibException $e
|
2019-01-31 18:54:17 +01:00
|
|
|
* @param string $program_name
|
2018-09-22 13:52:43 +02:00
|
|
|
* @param Entity\Station $station
|
|
|
|
*
|
2019-09-10 18:40:31 +02:00
|
|
|
* @throws AlreadyRunningException
|
2020-07-02 02:26:26 +02:00
|
|
|
* @throws BadNameException
|
2019-09-10 18:40:31 +02:00
|
|
|
* @throws NotRunningException
|
2020-07-02 02:26:26 +02:00
|
|
|
* @throws SupervisorException
|
2018-06-09 18:17:21 +02:00
|
|
|
*/
|
2020-07-03 22:24:04 +02:00
|
|
|
protected function handleSupervisorException(
|
2020-07-02 03:03:28 +02:00
|
|
|
SupervisorLibException $e,
|
2021-01-19 18:52:45 +01:00
|
|
|
string $program_name,
|
2020-07-02 03:03:28 +02:00
|
|
|
Entity\Station $station
|
|
|
|
): void {
|
2019-03-05 05:33:31 +01:00
|
|
|
$class_parts = explode('\\', static::class);
|
|
|
|
$class_name = array_pop($class_parts);
|
|
|
|
|
2020-07-02 03:03:28 +02:00
|
|
|
if ($e instanceof Fault\BadNameException) {
|
2022-05-07 18:44:14 +02:00
|
|
|
$e_headline = sprintf(
|
|
|
|
__('%s is not recognized as a service.'),
|
|
|
|
$class_name
|
|
|
|
);
|
2019-07-11 01:34:45 +02:00
|
|
|
$e_body = __('It may not be registered with Supervisor yet. Restarting broadcasting may help.');
|
|
|
|
|
2019-09-10 18:40:31 +02:00
|
|
|
$app_e = new BadNameException(
|
2019-09-04 20:00:51 +02:00
|
|
|
$e_headline . '; ' . $e_body,
|
2018-06-09 18:17:21 +02:00
|
|
|
$e->getCode(),
|
|
|
|
$e
|
|
|
|
);
|
2020-07-02 03:03:28 +02:00
|
|
|
} elseif ($e instanceof Fault\AlreadyStartedException) {
|
2022-05-07 18:44:14 +02:00
|
|
|
$e_headline = sprintf(
|
|
|
|
__('%s cannot start'),
|
|
|
|
$class_name
|
|
|
|
);
|
2019-12-07 01:57:50 +01:00
|
|
|
$e_body = __('It is already running.');
|
|
|
|
|
|
|
|
$app_e = new AlreadyRunningException(
|
|
|
|
$e_headline . '; ' . $e_body,
|
|
|
|
$e->getCode(),
|
|
|
|
$e
|
|
|
|
);
|
2020-07-02 03:03:28 +02:00
|
|
|
} elseif ($e instanceof Fault\NotRunningException) {
|
2022-05-07 18:44:14 +02:00
|
|
|
$e_headline = sprintf(
|
|
|
|
__('%s cannot stop'),
|
|
|
|
$class_name
|
|
|
|
);
|
2019-12-07 01:57:50 +01:00
|
|
|
$e_body = __('It is not running.');
|
|
|
|
|
|
|
|
$app_e = new NotRunningException(
|
|
|
|
$e_headline . '; ' . $e_body,
|
|
|
|
$e->getCode(),
|
|
|
|
$e
|
|
|
|
);
|
2018-06-09 18:17:21 +02:00
|
|
|
} else {
|
2022-05-07 18:44:14 +02:00
|
|
|
$e_headline = sprintf(
|
|
|
|
__('%s encountered an error'),
|
|
|
|
$class_name
|
|
|
|
);
|
2019-12-07 01:57:50 +01:00
|
|
|
|
|
|
|
// Get more detailed information for more significant errors.
|
|
|
|
$process_log = $this->supervisor->tailProcessStdoutLog($program_name, 0, 500);
|
2021-06-08 08:40:49 +02:00
|
|
|
$process_log = array_values(array_filter(explode("\n", $process_log[0])));
|
2019-12-07 01:57:50 +01:00
|
|
|
$process_log = array_slice($process_log, -6);
|
|
|
|
|
|
|
|
$e_body = (!empty($process_log))
|
|
|
|
? implode('<br>', $process_log)
|
|
|
|
: __('Check the log for details.');
|
|
|
|
|
|
|
|
$app_e = new SupervisorException($e_headline, $e->getCode(), $e);
|
|
|
|
$app_e->addExtraData('supervisor_log', $process_log);
|
|
|
|
$app_e->addExtraData('supervisor_process_info', $this->supervisor->getProcessInfo($program_name));
|
2018-06-09 18:17:21 +02:00
|
|
|
}
|
|
|
|
|
2019-09-04 20:00:51 +02:00
|
|
|
$app_e->setFormattedMessage('<b>' . $e_headline . '</b><br>' . $e_body);
|
2018-09-22 13:52:43 +02:00
|
|
|
$app_e->addLoggingContext('station_id', $station->getId());
|
|
|
|
$app_e->addLoggingContext('station_name', $station->getName());
|
2018-06-09 18:17:21 +02:00
|
|
|
|
|
|
|
throw $app_e;
|
|
|
|
}
|
2018-09-22 13:52:43 +02:00
|
|
|
|
|
|
|
/**
|
2019-09-04 20:00:51 +02:00
|
|
|
* Return the path where logs are written to.
|
2018-09-22 13:52:43 +02:00
|
|
|
*
|
2019-09-04 20:00:51 +02:00
|
|
|
* @param Entity\Station $station
|
2018-09-22 13:52:43 +02:00
|
|
|
*/
|
2019-09-04 20:00:51 +02:00
|
|
|
public function getLogPath(Entity\Station $station): string
|
2018-09-22 13:52:43 +02:00
|
|
|
{
|
2019-09-04 20:00:51 +02:00
|
|
|
$config_dir = $station->getRadioConfigDir();
|
|
|
|
|
|
|
|
$class_parts = explode('\\', static::class);
|
|
|
|
$class_name = array_pop($class_parts);
|
|
|
|
|
|
|
|
return $config_dir . '/' . strtolower($class_name) . '.log';
|
2018-09-22 13:52:43 +02:00
|
|
|
}
|
2018-08-05 00:05:14 +02:00
|
|
|
}
|