*/ class FilesController extends AbstractStationApiCrudController { protected string $entityClass = Entity\StationMedia::class; protected string $resourceRouteName = 'api:stations:file'; public function __construct( protected Adapters $adapters, protected MessageBus $messageBus, protected Entity\Repository\CustomFieldRepository $customFieldsRepo, protected Entity\Repository\StationMediaRepository $mediaRepo, protected Entity\Repository\StationPlaylistMediaRepository $playlistMediaRepo, ReloadableEntityManagerInterface $em, Serializer $serializer, ValidatorInterface $validator ) { parent::__construct($em, $serializer, $validator); } public function listAction(ServerRequest $request, Response $response): ResponseInterface { $storageLocation = $this->getStation($request)->getMediaStorageLocation(); $query = $this->em->createQuery( <<<'DQL' SELECT e FROM App\Entity\StationMedia e WHERE e.storage_location = :storageLocation DQL )->setParameter('storageLocation', $storageLocation); return $this->listPaginatedFromQuery($request, $response, $query); } public function createAction(ServerRequest $request, Response $response): ResponseInterface { $station = $this->getStation($request); $mediaStorage = $station->getMediaStorageLocation(); if ($mediaStorage->isStorageFull()) { return $response->withStatus(500) ->withJson(new Entity\Api\Error(500, __('This station is out of available storage space.'))); } $request->getParsedBody(); // Convert the body into an UploadFile API entity first. /** @var Entity\Api\UploadFile $api_record */ $api_record = $this->serializer->denormalize($request->getParsedBody(), Entity\Api\UploadFile::class, null, []); // Validate the UploadFile API record. $errors = $this->validator->validate($api_record); if (count($errors) > 0) { throw ValidationException::fromValidationErrors($errors); } // Write file to temp path. $temp_path = $station->getRadioTempDir() . '/' . $api_record->getSanitizedFilename(); file_put_contents($temp_path, $api_record->getFileContents()); // Process temp path as regular media record. $record = $this->mediaRepo->getOrCreate($station, $api_record->getSanitizedPath(), $temp_path); $return = $this->viewRecord($record, $request); return $response->withJson($return); } public function editAction( ServerRequest $request, Response $response, mixed $station_id, mixed $id ): ResponseInterface { $station = $this->getStation($request); $record = $this->getRecord($station, $id); if (null === $record) { return $response->withStatus(404) ->withJson(Entity\Api\Error::notFound()); } $data = $request->getParsedBody(); if (!is_array($data)) { throw new InvalidArgumentException('Could not parse input data.'); } $custom_fields = $data['custom_fields'] ?? null; $playlists = $data['playlists'] ?? null; unset($data['custom_fields'], $data['playlists']); $fsMedia = (new StationFilesystems($station))->getMediaFilesystem(); $record = $this->fromArray( $data, $record, [ AbstractNormalizer::CALLBACKS => [ 'path' => function ($new_value, $record) use ($fsMedia) { // Detect and handle a rename. if (($record instanceof Entity\StationMedia) && $new_value !== $record->getPath()) { $fsMedia->move($record->getPath(), $new_value); } return $new_value; }, ], ] ); $errors = $this->validator->validate($record); if (count($errors) > 0) { throw ValidationException::fromValidationErrors($errors); } if ($record instanceof Entity\StationMedia) { $this->mediaRepo->writeToFile($record); $this->em->persist($record); $this->em->flush(); if (null !== $custom_fields) { $this->customFieldsRepo->setCustomFields($record, $custom_fields); } if (null !== $playlists) { /** @var Entity\StationPlaylist[] $affected_playlists */ $affected_playlists = []; // Remove existing playlists. $media_playlists = $this->playlistMediaRepo->clearPlaylistsFromMedia($record, $station); $this->em->flush(); foreach ($media_playlists as $playlist_id => $playlist) { if (!isset($affected_playlists[$playlist_id])) { $affected_playlists[$playlist_id] = $playlist; } } // Set new playlists. foreach ($playlists as $new_playlist) { if (is_array($new_playlist)) { $playlist_id = $new_playlist['id']; $playlist_weight = $new_playlist['weight'] ?? 0; } else { $playlist_id = (int)$new_playlist; $playlist_weight = 0; } $playlist = $this->em->getRepository(Entity\StationPlaylist::class)->findOneBy( [ 'station' => $station, 'id' => $playlist_id, ] ); if ($playlist instanceof Entity\StationPlaylist) { $affected_playlists[$playlist->getId()] = $playlist; $this->playlistMediaRepo->addMediaToPlaylist($record, $playlist, $playlist_weight); } } $this->em->flush(); // Handle playlist changes. $backend = $this->adapters->getBackendAdapter($station); if ($backend instanceof Liquidsoap) { foreach ($affected_playlists as $playlist_id => $playlist_row) { // Instruct the message queue to start a new "write playlist to file" task. $message = new WritePlaylistFileMessage(); $message->playlist_id = $playlist_id; $this->messageBus->dispatch($message); } } } } return $response->withJson(Entity\Api\Status::updated()); } protected function createRecord(array $data, Entity\Station $station): object { $mediaStorage = $station->getMediaStorageLocation(); return $this->editRecord( $data, null, [ AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS => [ $this->entityClass => [ 'station' => $station, 'storageLocation' => $mediaStorage, ], ], ] ); } protected function getRecord(Entity\Station $station, int|string $id): ?object { $mediaStorage = $station->getMediaStorageLocation(); $repo = $this->em->getRepository($this->entityClass); foreach (['id', 'unique_id', 'song_id'] as $field) { $record = $repo->findOneBy( [ 'storage_location' => $mediaStorage, $field => $id, ] ); if ($record instanceof $this->entityClass) { return $record; } } return null; } /** @inheritDoc */ protected function toArray(object $record, array $context = []): array { $row = parent::toArray($record, $context); if ($record instanceof Entity\StationMedia) { $row['custom_fields'] = $this->customFieldsRepo->getCustomFields($record); } return $row; } /** * @inheritDoc */ protected function deleteRecord(object $record): void { if (!($record instanceof Entity\StationMedia)) { throw new InvalidArgumentException(sprintf('Record must be an instance of %s.', $this->entityClass)); } // Delete the media file off the filesystem. // Write new PLS playlist configuration. foreach ($this->mediaRepo->remove($record, true) as $playlist_id => $playlist) { $backend = $this->adapters->getBackendAdapter($playlist->getStation()); if ($backend instanceof Liquidsoap) { // Instruct the message queue to start a new "write playlist to file" task. $message = new WritePlaylistFileMessage(); $message->playlist_id = $playlist_id; $this->messageBus->dispatch($message); } } } }