| Total Complexity | 50 |
| Total Lines | 364 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like PlaylistApiController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use PlaylistApiController, and based on these observations, apply Extract Interface, too.
| 1 | <?php declare(strict_types=1); |
||
| 40 | class PlaylistApiController extends Controller { |
||
| 41 | private $urlGenerator; |
||
| 42 | private $playlistBusinessLayer; |
||
| 43 | private $artistBusinessLayer; |
||
| 44 | private $albumBusinessLayer; |
||
| 45 | private $trackBusinessLayer; |
||
| 46 | private $genreBusinessLayer; |
||
| 47 | private $coverHelper; |
||
| 48 | private $playlistFileService; |
||
| 49 | private $userId; |
||
| 50 | private $userFolder; |
||
| 51 | private $configManager; |
||
| 52 | private $logger; |
||
| 53 | |||
| 54 | public function __construct(string $appname, |
||
| 55 | IRequest $request, |
||
| 56 | IURLGenerator $urlGenerator, |
||
| 57 | PlaylistBusinessLayer $playlistBusinessLayer, |
||
| 58 | ArtistBusinessLayer $artistBusinessLayer, |
||
| 59 | AlbumBusinessLayer $albumBusinessLayer, |
||
| 60 | TrackBusinessLayer $trackBusinessLayer, |
||
| 61 | GenreBusinessLayer $genreBusinessLayer, |
||
| 62 | CoverHelper $coverHelper, |
||
| 63 | PlaylistFileService $playlistFileService, |
||
| 64 | string $userId, |
||
| 65 | Folder $userFolder, |
||
| 66 | IConfig $configManager, |
||
| 67 | Logger $logger) { |
||
| 68 | parent::__construct($appname, $request); |
||
| 69 | $this->urlGenerator = $urlGenerator; |
||
| 70 | $this->playlistBusinessLayer = $playlistBusinessLayer; |
||
| 71 | $this->artistBusinessLayer = $artistBusinessLayer; |
||
| 72 | $this->albumBusinessLayer = $albumBusinessLayer; |
||
| 73 | $this->trackBusinessLayer = $trackBusinessLayer; |
||
| 74 | $this->genreBusinessLayer = $genreBusinessLayer; |
||
| 75 | $this->coverHelper = $coverHelper; |
||
| 76 | $this->playlistFileService = $playlistFileService; |
||
| 77 | $this->userId = $userId; |
||
| 78 | $this->userFolder = $userFolder; |
||
| 79 | $this->configManager = $configManager; |
||
| 80 | $this->logger = $logger; |
||
| 81 | } |
||
| 82 | |||
| 83 | /** |
||
| 84 | * lists all playlists |
||
| 85 | * |
||
| 86 | * @NoAdminRequired |
||
| 87 | * @NoCSRFRequired |
||
| 88 | */ |
||
| 89 | public function getAll() { |
||
| 94 | } |
||
| 95 | |||
| 96 | /** |
||
| 97 | * creates a playlist |
||
| 98 | * |
||
| 99 | * @NoAdminRequired |
||
| 100 | * @NoCSRFRequired |
||
| 101 | */ |
||
| 102 | public function create($name, $trackIds) { |
||
| 103 | $playlist = $this->playlistBusinessLayer->create($name, $this->userId); |
||
| 104 | |||
| 105 | // add trackIds to the newly created playlist if provided |
||
| 106 | if (!empty($trackIds)) { |
||
| 107 | $playlist = $this->playlistBusinessLayer->addTracks( |
||
| 108 | self::toIntArray($trackIds), $playlist->getId(), $this->userId); |
||
| 109 | } |
||
| 110 | |||
| 111 | return $playlist->toAPI(); |
||
| 112 | } |
||
| 113 | |||
| 114 | /** |
||
| 115 | * deletes a playlist |
||
| 116 | * @param int $id playlist ID |
||
| 117 | * |
||
| 118 | * @NoAdminRequired |
||
| 119 | * @NoCSRFRequired |
||
| 120 | */ |
||
| 121 | public function delete(int $id) { |
||
| 122 | $this->playlistBusinessLayer->delete($id, $this->userId); |
||
| 123 | return []; |
||
| 124 | } |
||
| 125 | |||
| 126 | /** |
||
| 127 | * lists a single playlist |
||
| 128 | * @param int $id playlist ID |
||
| 129 | * |
||
| 130 | * @NoAdminRequired |
||
| 131 | * @NoCSRFRequired |
||
| 132 | */ |
||
| 133 | public function get(int $id, $fulltree) { |
||
| 134 | try { |
||
| 135 | $playlist = $this->playlistBusinessLayer->find($id, $this->userId); |
||
| 136 | |||
| 137 | $fulltree = \filter_var($fulltree, FILTER_VALIDATE_BOOLEAN); |
||
| 138 | if ($fulltree) { |
||
| 139 | return $this->toFullTree($playlist); |
||
| 140 | } else { |
||
| 141 | return $playlist->toAPI(); |
||
| 142 | } |
||
| 143 | } catch (BusinessLayerException $ex) { |
||
| 144 | return new ErrorResponse(Http::STATUS_NOT_FOUND, $ex->getMessage()); |
||
| 145 | } |
||
| 146 | } |
||
| 147 | |||
| 148 | private function toFullTree($playlist) { |
||
| 149 | $trackIds = $playlist->getTrackIdsAsArray(); |
||
| 150 | $tracks = $this->trackBusinessLayer->findById($trackIds, $this->userId); |
||
| 151 | $this->albumBusinessLayer->injectAlbumsToTracks($tracks, $this->userId); |
||
| 152 | |||
| 153 | $result = $playlist->toAPI(); |
||
| 154 | unset($result['trackIds']); |
||
| 155 | $result['tracks'] = Util::arrayMapMethod($tracks, 'toAPI', [$this->urlGenerator]); |
||
| 156 | |||
| 157 | return $result; |
||
| 158 | } |
||
| 159 | |||
| 160 | /** |
||
| 161 | * generate a smart playlist according to the given rules |
||
| 162 | * |
||
| 163 | * @NoAdminRequired |
||
| 164 | * @NoCSRFRequired |
||
| 165 | */ |
||
| 166 | public function generate(?bool $useLatestParams, ?string $playRate, ?string $genres, ?string $artists, ?int $fromYear, ?int $toYear, int $size=100) { |
||
| 167 | if ($useLatestParams) { |
||
| 168 | $playRate = $this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_play_rate') ?: null; |
||
| 169 | $genres = $this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_genres') ?: null; |
||
| 170 | $artists = $this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_artists') ?: null; |
||
| 171 | $fromYear = (int)$this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_from_year') ?: null; |
||
| 172 | $toYear = (int)$this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_to_year') ?: null; |
||
| 173 | $size = (int)$this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_size', 100); |
||
| 174 | } else { |
||
| 175 | $this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_play_rate', $playRate ?? ''); |
||
| 176 | $this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_genres', $genres ?? ''); |
||
| 177 | $this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_artists', $artists ?? ''); |
||
| 178 | $this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_from_year', (string)$fromYear); |
||
| 179 | $this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_to_year', (string)$toYear); |
||
| 180 | $this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_size', (string)$size); |
||
| 181 | } |
||
| 182 | |||
| 183 | // ensure the artists and genres contain only valid IDs |
||
| 184 | $genres = $this->genreBusinessLayer->findAllIds($this->userId, self::toIntArray($genres)); |
||
| 185 | $artists = $this->artistBusinessLayer->findAllIds($this->userId, self::toIntArray($artists)); |
||
| 186 | |||
| 187 | $playlist = $this->playlistBusinessLayer->generate( |
||
| 188 | $playRate, $genres, $artists, $fromYear, $toYear, $size, $this->userId); |
||
| 189 | $result = $playlist->toAPI(); |
||
| 190 | |||
| 191 | $result['params'] = [ |
||
| 192 | 'playRate' => $playRate ?: null, |
||
| 193 | 'genres' => \implode(',', $genres) ?: null, |
||
| 194 | 'artists' => \implode(',', $artists) ?: null, |
||
| 195 | 'fromYear' => $fromYear ?: null, |
||
| 196 | 'toYear' => $toYear ?: null, |
||
| 197 | 'size' => $size |
||
| 198 | ]; |
||
| 199 | |||
| 200 | return $result; |
||
| 201 | } |
||
| 202 | |||
| 203 | /** |
||
| 204 | * get cover image for a playlist |
||
| 205 | * @param int $id playlist ID |
||
| 206 | * |
||
| 207 | * @NoAdminRequired |
||
| 208 | * @NoCSRFRequired |
||
| 209 | */ |
||
| 210 | public function getCover(int $id) { |
||
| 211 | try { |
||
| 212 | $playlist = $this->playlistBusinessLayer->find($id, $this->userId); |
||
| 213 | $cover = $this->coverHelper->getCover($playlist, $this->userId, $this->userFolder); |
||
| 214 | |||
| 215 | if ($cover !== null) { |
||
| 216 | return new FileResponse($cover); |
||
| 217 | } else { |
||
| 218 | return new ErrorResponse(Http::STATUS_NOT_FOUND, 'The playlist has no cover art'); |
||
| 219 | } |
||
| 220 | } catch (BusinessLayerException $ex) { |
||
| 221 | return new ErrorResponse(Http::STATUS_NOT_FOUND, $ex->getMessage()); |
||
| 222 | } |
||
| 223 | } |
||
| 224 | |||
| 225 | /** |
||
| 226 | * update a playlist |
||
| 227 | * @param int $id playlist ID |
||
| 228 | * |
||
| 229 | * @NoAdminRequired |
||
| 230 | * @NoCSRFRequired |
||
| 231 | */ |
||
| 232 | public function update(int $id, string $name = null, string $comment = null, string $trackIds = null) { |
||
| 233 | $result = null; |
||
| 234 | if ($name !== null) { |
||
| 235 | $result = $this->modifyPlaylist('rename', [$name, $id, $this->userId]); |
||
| 236 | } |
||
| 237 | if ($comment !== null) { |
||
| 238 | $result = $this->modifyPlaylist('setComment', [$comment, $id, $this->userId]); |
||
| 239 | } |
||
| 240 | if ($trackIds!== null) { |
||
| 241 | $result = $this->modifyPlaylist('setTracks', [self::toIntArray($trackIds), $id, $this->userId]); |
||
| 242 | } |
||
| 243 | if ($result === null) { |
||
| 244 | $result = new ErrorResponse(Http::STATUS_BAD_REQUEST, "at least one of the args ['name', 'comment', 'trackIds'] must be given"); |
||
| 245 | } |
||
| 246 | return $result; |
||
| 247 | } |
||
| 248 | |||
| 249 | /** |
||
| 250 | * add tracks to a playlist |
||
| 251 | * @param int $id playlist ID |
||
| 252 | * |
||
| 253 | * @NoAdminRequired |
||
| 254 | * @NoCSRFRequired |
||
| 255 | */ |
||
| 256 | public function addTracks(int $id, $trackIds) { |
||
| 257 | return $this->modifyPlaylist('addTracks', [self::toIntArray($trackIds), $id, $this->userId]); |
||
| 258 | } |
||
| 259 | |||
| 260 | /** |
||
| 261 | * removes tracks from a playlist |
||
| 262 | * @param int $id playlist ID |
||
| 263 | * |
||
| 264 | * @NoAdminRequired |
||
| 265 | * @NoCSRFRequired |
||
| 266 | */ |
||
| 267 | public function removeTracks(int $id, $indices) { |
||
| 268 | return $this->modifyPlaylist('removeTracks', [self::toIntArray($indices), $id, $this->userId]); |
||
| 269 | } |
||
| 270 | |||
| 271 | /** |
||
| 272 | * moves single track on playlist to a new position |
||
| 273 | * @param int $id playlist ID |
||
| 274 | * |
||
| 275 | * @NoAdminRequired |
||
| 276 | * @NoCSRFRequired |
||
| 277 | */ |
||
| 278 | public function reorder(int $id, $fromIndex, $toIndex) { |
||
| 279 | return $this->modifyPlaylist('moveTrack', |
||
| 280 | [$fromIndex, $toIndex, $id, $this->userId]); |
||
| 281 | } |
||
| 282 | |||
| 283 | /** |
||
| 284 | * export the playlist to a file |
||
| 285 | * @param int $id playlist ID |
||
| 286 | * @param string $path parent folder path |
||
| 287 | * @param string $oncollision action to take on file name collision, |
||
| 288 | * supported values: |
||
| 289 | * - 'overwrite' The existing file will be overwritten |
||
| 290 | * - 'keepboth' The new file is named with a suffix to make it unique |
||
| 291 | * - 'abort' (default) The operation will fail |
||
| 292 | * |
||
| 293 | * @NoAdminRequired |
||
| 294 | * @NoCSRFRequired |
||
| 295 | */ |
||
| 296 | public function exportToFile(int $id, string $path, string $oncollision) { |
||
| 297 | try { |
||
| 298 | $exportedFilePath = $this->playlistFileService->exportToFile( |
||
| 299 | $id, $this->userId, $this->userFolder, $path, $oncollision); |
||
| 300 | return new JSONResponse(['wrote_to_file' => $exportedFilePath]); |
||
| 301 | } catch (BusinessLayerException $ex) { |
||
| 302 | return new ErrorResponse(Http::STATUS_NOT_FOUND, 'playlist not found'); |
||
| 303 | } catch (\OCP\Files\NotFoundException $ex) { |
||
| 304 | return new ErrorResponse(Http::STATUS_NOT_FOUND, 'folder not found'); |
||
| 305 | } catch (\RuntimeException $ex) { |
||
| 306 | return new ErrorResponse(Http::STATUS_CONFLICT, $ex->getMessage()); |
||
| 307 | } catch (\OCP\Files\NotPermittedException $ex) { |
||
| 308 | return new ErrorResponse(Http::STATUS_FORBIDDEN, 'user is not allowed to write to the target file'); |
||
| 309 | } |
||
| 310 | } |
||
| 311 | |||
| 312 | /** |
||
| 313 | * import playlist contents from a file |
||
| 314 | * @param int $id playlist ID |
||
| 315 | * @param string $filePath path of the file to import |
||
| 316 | * |
||
| 317 | * @NoAdminRequired |
||
| 318 | * @NoCSRFRequired |
||
| 319 | */ |
||
| 320 | public function importFromFile(int $id, string $filePath) { |
||
| 321 | try { |
||
| 322 | $result = $this->playlistFileService->importFromFile($id, $this->userId, $this->userFolder, $filePath); |
||
| 323 | $result['playlist'] = $result['playlist']->toAPI(); |
||
| 324 | return $result; |
||
| 325 | } catch (BusinessLayerException $ex) { |
||
| 326 | return new ErrorResponse(Http::STATUS_NOT_FOUND, 'playlist not found'); |
||
| 327 | } catch (\OCP\Files\NotFoundException $ex) { |
||
| 328 | return new ErrorResponse(Http::STATUS_NOT_FOUND, 'playlist file not found'); |
||
| 329 | } catch (\UnexpectedValueException $ex) { |
||
| 330 | return new ErrorResponse(Http::STATUS_UNSUPPORTED_MEDIA_TYPE, $ex->getMessage()); |
||
| 331 | } |
||
| 332 | } |
||
| 333 | |||
| 334 | /** |
||
| 335 | * read and parse a playlist file |
||
| 336 | * @param int $fileId ID of the file to parse |
||
| 337 | * |
||
| 338 | * @NoAdminRequired |
||
| 339 | * @NoCSRFRequired |
||
| 340 | */ |
||
| 341 | public function parseFile(int $fileId) { |
||
| 342 | try { |
||
| 343 | $result = $this->playlistFileService->parseFile($fileId, $this->userFolder); |
||
| 344 | |||
| 345 | // Make a lookup table of all the file IDs in the user library to avoid having to run |
||
| 346 | // a DB query for each track in the playlist to check if it is in the library. This |
||
| 347 | // could make a difference in case of a huge playlist. |
||
| 348 | $libFileIds = $this->trackBusinessLayer->findAllFileIds($this->userId); |
||
| 349 | $libFileIds = \array_flip($libFileIds); |
||
| 350 | |||
| 351 | $bogusUrlId = -1; |
||
| 352 | |||
| 353 | // compose the final result |
||
| 354 | $result['files'] = \array_map(function ($fileInfo) use ($libFileIds, &$bogusUrlId) { |
||
| 355 | if (isset($fileInfo['url'])) { |
||
| 356 | $fileInfo['id'] = $bogusUrlId--; |
||
| 357 | $fileInfo['mimetype'] = null; |
||
| 358 | return $fileInfo; |
||
| 359 | } else { |
||
| 360 | $file = $fileInfo['file']; |
||
| 361 | return [ |
||
| 362 | 'id' => $file->getId(), |
||
| 363 | 'name' => $file->getName(), |
||
| 364 | 'path' => $this->userFolder->getRelativePath($file->getParent()->getPath()), |
||
| 365 | 'mimetype' => $file->getMimeType(), |
||
| 366 | 'caption' => $fileInfo['caption'], |
||
| 367 | 'in_library' => isset($libFileIds[$file->getId()]) |
||
| 368 | ]; |
||
| 369 | } |
||
| 370 | }, $result['files']); |
||
| 371 | return new JSONResponse($result); |
||
| 372 | } catch (\OCP\Files\NotFoundException $ex) { |
||
| 373 | return new ErrorResponse(Http::STATUS_NOT_FOUND, 'playlist file not found'); |
||
| 374 | } catch (\UnexpectedValueException $ex) { |
||
| 375 | return new ErrorResponse(Http::STATUS_UNSUPPORTED_MEDIA_TYPE, $ex->getMessage()); |
||
| 376 | } |
||
| 377 | } |
||
| 378 | |||
| 379 | /** |
||
| 380 | * Modify playlist by calling a supplied method from PlaylistBusinessLayer |
||
| 381 | * @param string $funcName Name of a function to call from PlaylistBusinessLayer |
||
| 382 | * @param array $funcParams Parameters to pass to the function 'funcName' |
||
| 383 | * @return JSONResponse JSON representation of the modified playlist |
||
| 384 | */ |
||
| 385 | private function modifyPlaylist(string $funcName, array $funcParams) : JSONResponse { |
||
| 391 | } |
||
| 392 | } |
||
| 393 | |||
| 394 | /** |
||
| 395 | * Get integer array passed as parameter to the Playlist API |
||
| 396 | * @param string|int|null $listAsString Comma-separated integer values in string, or a single integer |
||
| 397 | * @return int[] |
||
| 398 | */ |
||
| 399 | private static function toIntArray(/*mixed*/ $listAsString) : array { |
||
| 400 | if ($listAsString === null || $listAsString === '') { |
||
| 401 | return []; |
||
| 402 | } else { |
||
| 404 | } |
||
| 405 | } |
||
| 406 | } |
||
| 407 |