Completed
Pull Request — master (#777)
by Pauli
13:39 queued 11:25
created

PlaylistApiController   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 296
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 109
dl 0
loc 296
ccs 0
cts 107
cp 0
rs 9.84
c 2
b 0
f 0
wmc 32

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getAll() 0 5 1
A __construct() 0 23 1
A delete() 0 3 1
A create() 0 10 2
A get() 0 12 3
A toFullTree() 0 15 2
A removeTracks() 0 2 1
A update() 0 12 4
A reorder() 0 3 1
A addTracks() 0 2 1
A modifyPlaylist() 0 6 2
A importFromFile() 0 14 4
A exportToFile() 0 17 5
A toIntArray() 0 2 1
A parseFile() 0 30 3
1
<?php
2
3
/**
4
 * ownCloud - Music app
5
 *
6
 * This file is licensed under the Affero General Public License version 3 or
7
 * later. See the COPYING file.
8
 *
9
 * @author Morris Jobke <[email protected]>
10
 * @author Pauli Järvinen <[email protected]>
11
 * @copyright Morris Jobke 2013, 2014
12
 * @copyright Pauli Järvinen 2017 - 2020
13
 */
14
15
namespace OCA\Music\Controller;
16
17
use \OCP\AppFramework\Controller;
0 ignored issues
show
Bug introduced by
The type OCP\AppFramework\Controller was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
use \OCP\AppFramework\Http;
0 ignored issues
show
Bug introduced by
The type OCP\AppFramework\Http was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
19
use \OCP\AppFramework\Http\JSONResponse;
0 ignored issues
show
Bug introduced by
The type OCP\AppFramework\Http\JSONResponse was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
20
21
use \OCP\Files\Folder;
0 ignored issues
show
Bug introduced by
The type OCP\Files\Folder was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
use \OCP\IRequest;
0 ignored issues
show
Bug introduced by
The type OCP\IRequest was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
23
use \OCP\IURLGenerator;
0 ignored issues
show
Bug introduced by
The type OCP\IURLGenerator was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
24
25
use \OCA\Music\AppFramework\BusinessLayer\BusinessLayerException;
26
use \OCA\Music\AppFramework\Core\Logger;
27
use \OCA\Music\BusinessLayer\AlbumBusinessLayer;
28
use \OCA\Music\BusinessLayer\ArtistBusinessLayer;
29
use \OCA\Music\BusinessLayer\PlaylistBusinessLayer;
30
use \OCA\Music\BusinessLayer\TrackBusinessLayer;
31
use \OCA\Music\Db\Playlist;
32
use \OCA\Music\Http\ErrorResponse;
33
use \OCA\Music\Utility\APISerializer;
34
use \OCA\Music\Utility\PlaylistFileService;
35
36
class PlaylistApiController extends Controller {
37
	private $urlGenerator;
38
	private $playlistBusinessLayer;
39
	private $artistBusinessLayer;
40
	private $albumBusinessLayer;
41
	private $trackBusinessLayer;
42
	private $playlistFileService;
43
	private $userId;
44
	private $userFolder;
45
	private $l10n;
46
	private $logger;
47
48
	public function __construct($appname,
49
								IRequest $request,
50
								IURLGenerator $urlGenerator,
51
								PlaylistBusinessLayer $playlistBusinessLayer,
52
								ArtistBusinessLayer $artistBusinessLayer,
53
								AlbumBusinessLayer $albumBusinessLayer,
54
								TrackBusinessLayer $trackBusinessLayer,
55
								PlaylistFileService $playlistFileService,
56
								$userId,
57
								Folder $userFolder,
58
								$l10n,
59
								Logger $logger) {
60
		parent::__construct($appname, $request);
61
		$this->urlGenerator = $urlGenerator;
62
		$this->playlistBusinessLayer = $playlistBusinessLayer;
63
		$this->artistBusinessLayer = $artistBusinessLayer;
64
		$this->albumBusinessLayer = $albumBusinessLayer;
65
		$this->trackBusinessLayer = $trackBusinessLayer;
66
		$this->playlistFileService = $playlistFileService;
67
		$this->userId = $userId;
68
		$this->userFolder = $userFolder;
69
		$this->l10n = $l10n;
70
		$this->logger = $logger;
71
	}
72
73
	/**
74
	 * lists all playlists
75
	 *
76
	 * @NoAdminRequired
77
	 * @NoCSRFRequired
78
	 */
79
	public function getAll() {
80
		$playlists = $this->playlistBusinessLayer->findAll($this->userId);
81
		$serializer = new APISerializer();
82
83
		return $serializer->serialize($playlists);
84
	}
85
86
	/**
87
	 * creates a playlist
88
	 *
89
	 * @NoAdminRequired
90
	 * @NoCSRFRequired
91
	 */
92
	public function create($name, $trackIds) {
93
		$playlist = $this->playlistBusinessLayer->create($name, $this->userId);
94
95
		// add trackIds to the newly created playlist if provided
96
		if (!empty($trackIds)) {
97
			$playlist = $this->playlistBusinessLayer->addTracks(
98
					self::toIntArray($trackIds), $playlist->getId(), $this->userId);
99
		}
100
101
		return $playlist->toAPI();
102
	}
103
104
	/**
105
	 * deletes a playlist
106
	 * @param  int $id playlist ID
107
	 *
108
	 * @NoAdminRequired
109
	 * @NoCSRFRequired
110
	 */
111
	public function delete($id) {
112
		$this->playlistBusinessLayer->delete($id, $this->userId);
113
		return [];
114
	}
115
116
	/**
117
	 * lists a single playlist
118
	 * @param  int $id playlist ID
119
	 *
120
	 * @NoAdminRequired
121
	 * @NoCSRFRequired
122
	 */
123
	public function get($id, $fulltree) {
124
		try {
125
			$playlist = $this->playlistBusinessLayer->find($id, $this->userId);
126
127
			$fulltree = \filter_var($fulltree, FILTER_VALIDATE_BOOLEAN);
128
			if ($fulltree) {
129
				return $this->toFullTree($playlist);
130
			} else {
131
				return $playlist->toAPI();
132
			}
133
		} catch (BusinessLayerException $ex) {
134
			return new ErrorResponse(Http::STATUS_NOT_FOUND, $ex->getMessage());
135
		}
136
	}
137
138
	private function toFullTree($playlist) {
139
		$songs = [];
140
141
		// Get all track information for all the tracks of the playlist
142
		foreach ($playlist->getTrackIdsAsArray() as $trackId) {
143
			$song = $this->trackBusinessLayer->find($trackId, $this->userId);
144
			$song->setAlbum($this->albumBusinessLayer->find($song->getAlbumId(), $this->userId));
145
			$songs[] = $song->toAPI($this->urlGenerator);
146
		}
147
148
		$result = $playlist->toAPI();
149
		unset($result['trackIds']);
150
		$result['tracks'] = $songs;
151
152
		return $result;
153
	}
154
155
	/**
156
	 * update a playlist
157
	 * @param int $id playlist ID
158
	 * @param string|null $name
159
	 * @param string|null $comment
160
	 *
161
	 * @NoAdminRequired
162
	 * @NoCSRFRequired
163
	 */
164
	public function update($id, $name, $comment) {
165
		$result = null;
166
		if ($name !== null) {
167
			$result = $this->modifyPlaylist('rename', [$name, $id, $this->userId]); 
168
		}
169
		if ($comment !== null) {
170
			$result = $this->modifyPlaylist('setComment', [$comment, $id, $this->userId]);
171
		}
172
		if ($result === null) {
173
			$result = new ErrorResponse(Http::STATUS_BAD_REQUEST, "at least one of the args ['name', 'comment'] must be given");
174
		}
175
		return $result;
176
	}
177
178
	/**
179
	 * add tracks to a playlist
180
	 * @param  int $id playlist ID
181
	 *
182
	 * @NoAdminRequired
183
	 * @NoCSRFRequired
184
	 */
185
	public function addTracks($id, $trackIds) {
186
		return $this->modifyPlaylist('addTracks', [self::toIntArray($trackIds), $id, $this->userId]);
187
	}
188
189
	/**
190
	 * removes tracks from a playlist
191
	 * @param  int $id playlist ID
192
	 *
193
	 * @NoAdminRequired
194
	 * @NoCSRFRequired
195
	 */
196
	public function removeTracks($id, $indices) {
197
		return $this->modifyPlaylist('removeTracks', [self::toIntArray($indices), $id, $this->userId]);
198
	}
199
200
	/**
201
	 * moves single track on playlist to a new position
202
	 * @param  int $id playlist ID
203
	 *
204
	 * @NoAdminRequired
205
	 * @NoCSRFRequired
206
	 */
207
	public function reorder($id, $fromIndex, $toIndex) {
208
		return $this->modifyPlaylist('moveTrack',
209
				[$fromIndex, $toIndex, $id, $this->userId]);
210
	}
211
212
	/**
213
	 * export the playlist to a file
214
	 * @param int $id playlist ID
215
	 * @param string $path parent folder path
216
	 * @param string $oncollision action to take on file name collision,
217
	 *								supported values:
218
	 *								- 'overwrite' The existing file will be overwritten
219
	 *								- 'keepboth' The new file is named with a suffix to make it unique
220
	 *								- 'abort' (default) The operation will fail
221
	 *
222
	 * @NoAdminRequired
223
	 * @NoCSRFRequired
224
	 */
225
	public function exportToFile($id, $path, $oncollision) {
226
		try {
227
			$exportedFilePath = $this->playlistFileService->exportToFile(
228
					$id, $this->userId, $this->userFolder, $path, $oncollision);
229
			return new JSONResponse(['wrote_to_file' => $exportedFilePath]);
230
		}
231
		catch (BusinessLayerException $ex) {
232
			return new ErrorResponse(Http::STATUS_NOT_FOUND, 'playlist not found');
233
		}
234
		catch (\OCP\Files\NotFoundException $ex) {
0 ignored issues
show
Bug introduced by
The type OCP\Files\NotFoundException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
235
			return new ErrorResponse(Http::STATUS_NOT_FOUND, 'folder not found');
236
		}
237
		catch (\RuntimeException $ex) {
238
			return new ErrorResponse(Http::STATUS_CONFLICT, $ex->getMessage());
239
		}
240
		catch (\OCP\Files\NotPermittedException $ex) {
0 ignored issues
show
Bug introduced by
The type OCP\Files\NotPermittedException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
241
			return new ErrorResponse(Http::STATUS_FORBIDDEN, 'user is not allowed to write to the target file');
242
		}
243
	}
244
245
	/**
246
	 * import playlist contents from a file
247
	 * @param int $id playlist ID
248
	 * @param string $filePath path of the file to import
249
	 *
250
	 * @NoAdminRequired
251
	 * @NoCSRFRequired
252
	 */
253
	public function importFromFile($id, $filePath) {
254
		try {
255
			$result = $this->playlistFileService->importFromFile($id, $this->userId, $this->userFolder, $filePath);
256
			$result['playlist'] = $result['playlist']->toAPI();
257
			return $result;
258
		}
259
		catch (BusinessLayerException $ex) {
260
			return new ErrorResponse(Http::STATUS_NOT_FOUND, 'playlist not found');
261
		}
262
		catch (\OCP\Files\NotFoundException $ex) {
263
			return new ErrorResponse(Http::STATUS_NOT_FOUND, 'playlist file not found');
264
		}
265
		catch (\UnexpectedValueException $ex) {
266
			return new ErrorResponse(Http::STATUS_UNSUPPORTED_MEDIA_TYPE, $ex->getMessage());
267
		}
268
	}
269
270
	/**
271
	 * read and parse a playlist file
272
	 * @param int $fileId ID of the file to parse
273
	 *
274
	 * @NoAdminRequired
275
	 * @NoCSRFRequired
276
	 */
277
	public function parseFile($fileId) {
278
279
		try {
280
			$result = $this->playlistFileService->parseFile($fileId, $this->userFolder);
281
282
			// Make a lookup table of all the file IDs in the user library to avoid having to run
283
			// a DB query for each track in the playlist to check if it is in the library. This
284
			// could make a difference in case of a huge playlist.
285
			$libFileIds = $this->trackBusinessLayer->findAllFileIds($this->userId);
286
			$libFileIds = \array_flip($libFileIds);
287
288
			// compose the final result
289
			$result['files'] = \array_map(function($fileAndCaption) use ($libFileIds) {
290
				$file = $fileAndCaption['file'];
291
				return [
292
					'id' => $file->getId(),
293
					'name' => $file->getName(),
294
					'path' => $this->userFolder->getRelativePath($file->getParent()->getPath()),
295
					'mimetype' => $file->getMimeType(),
296
					'caption' => $fileAndCaption['caption'],
297
					'in_library' => isset($libFileIds[$file->getId()])
298
				];
299
			}, $result['files']);
300
			return new JSONResponse($result);
301
		}
302
		catch (\OCP\Files\NotFoundException $ex) {
303
			return new ErrorResponse(Http::STATUS_NOT_FOUND, 'playlist file not found');
304
		}
305
		catch (\UnexpectedValueException $ex) {
306
			return new ErrorResponse(Http::STATUS_UNSUPPORTED_MEDIA_TYPE, $ex->getMessage());
307
		}
308
	}
309
310
	/**
311
	 * Modify playlist by calling a supplied method from PlaylistBusinessLayer
312
	 * @param string $funcName  Name of a function to call from PlaylistBusinessLayer
313
	 * @param array $funcParams Parameters to pass to the function 'funcName'
314
	 * @return \OCP\AppFramework\Http\JSONResponse JSON representation of the modified playlist
315
	 */
316
	private function modifyPlaylist($funcName, $funcParams) {
317
		try {
318
			$playlist = \call_user_func_array([$this->playlistBusinessLayer, $funcName], $funcParams);
319
			return $playlist->toAPI();
320
		} catch (BusinessLayerException $ex) {
321
			return new ErrorResponse(Http::STATUS_NOT_FOUND, $ex->getMessage());
322
		}
323
	}
324
325
	/**
326
	 * Get integer array passed as parameter to the Playlist API
327
	 * @param string $listAsString Comma-separated integer values in string
328
	 * @return int[]
329
	 */
330
	private static function toIntArray($listAsString) {
331
		return \array_map('intval', \explode(',', $listAsString));
332
	}
333
}
334