*/ class StationsController extends AbstractAdminApiCrudController { protected string $entityClass = Entity\Station::class; protected string $resourceRouteName = 'api:admin:station'; public function __construct( protected Entity\Repository\StationRepository $stationRepo, protected Entity\Repository\StorageLocationRepository $storageLocationRepo, protected Adapters $adapters, protected Configuration $configuration, protected ReloadableEntityManagerInterface $reloadableEm, Serializer $serializer, ValidatorInterface $validator ) { parent::__construct($reloadableEm, $serializer, $validator); } protected function viewRecord(object $record, ServerRequest $request): mixed { if (!($record instanceof $this->entityClass)) { throw new InvalidArgumentException(sprintf('Record must be an instance of %s.', $this->entityClass)); } $return = $this->toArray($record); $isInternal = ('true' === $request->getParam('internal', 'false')); $router = $request->getRouter(); $return['links'] = [ 'self' => (string)$router->fromHere( route_name: $this->resourceRouteName, route_params: ['id' => $record->getIdRequired()], absolute: !$isInternal ), 'manage' => (string)$router->named( route_name: 'stations:index:index', route_params: ['station_id' => $record->getIdRequired()], absolute: !$isInternal ), 'clone' => (string)$router->fromHere( route_name: 'api:admin:station:clone', route_params: ['id' => $record->getIdRequired()], absolute: !$isInternal ), ]; return $return; } /** * @param Entity\Station $record * @param array $context * * @return array */ protected function toArray(object $record, array $context = []): array { $context[AbstractNormalizer::IGNORED_ATTRIBUTES] = [ 'adapter_api_key', 'nowplaying', 'nowplaying_timestamp', 'automation_timestamp', 'needs_restart', 'has_started', ]; foreach (Entity\Station::getStorageLocationTypes() as $storageLocationType => $locationKey) { $context[AbstractNormalizer::CALLBACKS][$locationKey] = fn( array $value ) => $value['id']; } return parent::toArray($record, $context); } protected function fromArray(array $data, ?object $record = null, array $context = []): object { foreach (Entity\Station::getStorageLocationTypes() as $locationKey) { $idKey = $locationKey . '_id'; if (!empty($data[$idKey])) { $data[$locationKey] = $data[$idKey]; } unset($data[$idKey]); } return parent::fromArray($data, $record, $context); } /** * @param array|null $data * @param Entity\Station|null $record * @param array $context * * @return Entity\Station */ protected function editRecord(?array $data, object $record = null, array $context = []): object { $create_mode = (null === $record); if (null === $data) { throw new InvalidArgumentException('Could not parse input data.'); } $record = $this->fromArray($data, $record, $context); $errors = $this->validator->validate($record); if (count($errors) > 0) { throw ValidationException::fromValidationErrors($errors); } return ($create_mode) ? $this->handleCreate($record) : $this->handleEdit($record); } /** * @param Entity\Station $record */ protected function deleteRecord(object $record): void { $this->handleDelete($record); } protected function handleEdit(Entity\Station $station): Entity\Station { $original_record = $this->em->getUnitOfWork()->getOriginalEntityData($station); $this->em->persist($station); $this->em->flush(); $this->configuration->initializeConfiguration($station); // Delete media-related items if the media storage is changed. /** @var Entity\StorageLocation|null $oldMediaStorage */ $oldMediaStorage = $original_record['media_storage_location']; $newMediaStorage = $station->getMediaStorageLocation(); if (null === $oldMediaStorage || $oldMediaStorage->getId() !== $newMediaStorage->getId()) { $this->stationRepo->flushRelatedMedia($station); } // Get the original values to check for changes. $old_frontend = $original_record['frontend_type']; $old_backend = $original_record['backend_type']; $frontend_changed = ($old_frontend !== $station->getFrontendType()); $backend_changed = ($old_backend !== $station->getBackendType()); $adapter_changed = $frontend_changed || $backend_changed; if ($frontend_changed) { $frontend = $this->adapters->getFrontendAdapter($station); $this->stationRepo->resetMounts($station, $frontend); } if ($adapter_changed || !$station->getIsEnabled()) { $this->configuration->writeConfiguration($station, true); } return $station; } protected function handleCreate(Entity\Station $station): Entity\Station { $station->generateAdapterApiKey(); $this->em->persist($station); $this->em->flush(); $this->configuration->initializeConfiguration($station); // Create default mountpoints if station supports them. $frontend_adapter = $this->adapters->getFrontendAdapter($station); $this->stationRepo->resetMounts($station, $frontend_adapter); return $station; } protected function handleDelete(Entity\Station $station): void { $this->configuration->removeConfiguration($station); // Remove media folders. $radio_dir = $station->getRadioBaseDir(); File::rmdirRecursive($radio_dir); // Save changes and continue to the last setup step. $this->em->flush(); foreach ($station->getAllStorageLocations() as $storageLocation) { $stations = $this->storageLocationRepo->getStationsUsingLocation($storageLocation); if (1 === count($stations)) { $this->em->remove($storageLocation); } } $this->em->remove($station); $this->em->flush(); } }