Passed
Push — master ( 1bf36a...3f521f )
by Pauli
02:26
created

AmpacheController::xmlApi()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 8
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\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...
19
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...
20
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...
21
22
use \OCA\Music\AppFramework\BusinessLayer\BusinessLayer;
23
use \OCA\Music\AppFramework\BusinessLayer\BusinessLayerException;
24
use \OCA\Music\AppFramework\Core\Logger;
25
use \OCA\Music\Middleware\AmpacheException;
26
27
use \OCA\Music\BusinessLayer\AlbumBusinessLayer;
28
use \OCA\Music\BusinessLayer\ArtistBusinessLayer;
29
use \OCA\Music\BusinessLayer\GenreBusinessLayer;
30
use \OCA\Music\BusinessLayer\Library;
31
use \OCA\Music\BusinessLayer\PlaylistBusinessLayer;
32
use \OCA\Music\BusinessLayer\TrackBusinessLayer;
33
34
use \OCA\Music\Db\AmpacheUserMapper;
35
use \OCA\Music\Db\AmpacheSession;
36
use \OCA\Music\Db\AmpacheSessionMapper;
37
use \OCA\Music\Db\SortBy;
38
39
use \OCA\Music\Http\ErrorResponse;
40
use \OCA\Music\Http\FileResponse;
41
use \OCA\Music\Http\XMLResponse;
42
43
use \OCA\Music\Utility\AmpacheUser;
44
use \OCA\Music\Utility\CoverHelper;
45
use \OCA\Music\Utility\Util;
46
47
class AmpacheController extends Controller {
48
	private $ampacheUserMapper;
49
	private $ampacheSessionMapper;
50
	private $albumBusinessLayer;
51
	private $artistBusinessLayer;
52
	private $genreBusinessLayer;
53
	private $playlistBusinessLayer;
54
	private $trackBusinessLayer;
55
	private $library;
56
	private $ampacheUser;
57
	private $urlGenerator;
58
	private $rootFolder;
59
	private $l10n;
60
	private $coverHelper;
61
	private $logger;
62
	private $jsonMode;
63
64
	const SESSION_EXPIRY_TIME = 6000;
65
	const ALL_TRACKS_PLAYLIST_ID = 10000000;
66
	const API_VERSION = 350001;
67
68
	public function __construct($appname,
69
								IRequest $request,
70
								$l10n,
71
								IURLGenerator $urlGenerator,
72
								AmpacheUserMapper $ampacheUserMapper,
73
								AmpacheSessionMapper $ampacheSessionMapper,
74
								AlbumBusinessLayer $albumBusinessLayer,
75
								ArtistBusinessLayer $artistBusinessLayer,
76
								GenreBusinessLayer $genreBusinessLayer,
77
								PlaylistBusinessLayer $playlistBusinessLayer,
78
								TrackBusinessLayer $trackBusinessLayer,
79
								Library $library,
80
								AmpacheUser $ampacheUser,
81
								$rootFolder,
82
								CoverHelper $coverHelper,
83
								Logger $logger) {
84
		parent::__construct($appname, $request);
85
86
		$this->ampacheUserMapper = $ampacheUserMapper;
87
		$this->ampacheSessionMapper = $ampacheSessionMapper;
88
		$this->albumBusinessLayer = $albumBusinessLayer;
89
		$this->artistBusinessLayer = $artistBusinessLayer;
90
		$this->genreBusinessLayer = $genreBusinessLayer;
91
		$this->playlistBusinessLayer = $playlistBusinessLayer;
92
		$this->trackBusinessLayer = $trackBusinessLayer;
93
		$this->library = $library;
94
		$this->urlGenerator = $urlGenerator;
95
		$this->l10n = $l10n;
96
97
		// used to share user info with middleware
98
		$this->ampacheUser = $ampacheUser;
99
100
		// used to deliver actual media file
101
		$this->rootFolder = $rootFolder;
102
103
		$this->coverHelper = $coverHelper;
104
		$this->logger = $logger;
105
	}
106
107
	public function setJsonMode($useJsonMode) {
108
		$this->jsonMode = $useJsonMode;
109
	}
110
111
	public function ampacheResponse($content) {
112
		if ($this->jsonMode) {
113
			return new JSONResponse($content);
114
		} else {
115
			return new XMLResponse(['root' => $content], ['id', 'count']);
116
		}
117
	}
118
119
	/**
120
	 * @NoAdminRequired
121
	 * @PublicPage
122
	 * @NoCSRFRequired
123
	 */
124
	public function xmlApi($action, $user, $timestamp, $auth, $filter, $exact, $limit, $offset) {
125
		// differentation between xmlApi and jsonApi is made already by the middleware
126
		return $this->dispatch($action, $user, $timestamp, $auth, $filter, $exact, $limit, $offset);
127
	}
128
129
	/**
130
	 * @NoAdminRequired
131
	 * @PublicPage
132
	 * @NoCSRFRequired
133
	 */
134
	public function jsonApi($action, $user, $timestamp, $auth, $filter, $exact, $limit, $offset) {
135
		// differentation between xmlApi and jsonApi is made already by the middleware
136
		return $this->dispatch($action, $user, $timestamp, $auth, $filter, $exact, $limit, $offset);
137
	}
138
139
	protected function dispatch($action, $user, $timestamp, $auth, $filter, $exact, $limit, $offset) {
140
		$this->logger->log("Ampache action '$action' requested", 'debug');
141
142
		$limit = self::validateLimitOrOffset($limit);
143
		$offset = self::validateLimitOrOffset($offset);
144
145
		switch ($action) {
146
			case 'handshake':
147
				return $this->handshake($user, $timestamp, $auth);
148
			case 'ping':
149
				return $this->ping($auth);
150
			case 'artists':
151
				return $this->artists($filter, $exact, $limit, $offset);
152
			case 'artist':
153
				return $this->artist($filter);
154
			case 'artist_albums':
155
				return $this->artist_albums($filter, $auth);
156
			case 'album_songs':
157
				return $this->album_songs($filter, $auth);
158
			case 'albums':
159
				return $this->albums($filter, $exact, $limit, $offset, $auth);
160
			case 'album':
161
				return $this->album($filter, $auth);
162
			case 'artist_songs':
163
				return $this->artist_songs($filter, $auth);
164
			case 'songs':
165
				return $this->songs($filter, $exact, $limit, $offset, $auth);
166
			case 'song':
167
				return $this->song($filter, $auth);
168
			case 'search_songs':
169
				return $this->search_songs($filter, $auth);
170
			case 'playlists':
171
				return $this->playlists($filter, $exact, $limit, $offset);
172
			case 'playlist':
173
				return $this->playlist($filter);
174
			case 'playlist_songs':
175
				return $this->playlist_songs($filter, $limit, $offset, $auth);
176
			case 'tags':
177
				return $this->tags($filter, $exact, $limit, $offset);
178
			case 'tag':
179
				return $this->tag($filter);
180
			case 'tag_artists':
181
				return $this->tag_artists($filter, $limit, $offset);
182
			case 'tag_albums':
183
				return $this->tag_albums($filter, $limit, $offset, $auth);
184
			case 'tag_songs':
185
				return $this->tag_songs($filter, $limit, $offset, $auth);
186
			# non Ampache API action - used for provide the file
187
			case 'play':
188
				return $this->play($filter);
189
			case '_get_cover':
190
				return $this->get_cover($filter);
191
		}
192
193
		$this->logger->log("Unsupported Ampache action '$action' requested", 'warn');
194
		throw new AmpacheException('Action not supported', 405);
195
	}
196
197
	/***********************
198
	 * Ampahce API methods *
199
	 ***********************/
200
201
	protected function handshake($user, $timestamp, $auth) {
202
		$currentTime = \time();
203
		$expiryDate = $currentTime + self::SESSION_EXPIRY_TIME;
204
205
		$this->checkHandshakeTimestamp($timestamp, $currentTime);
206
		$this->checkHandshakeAuthentication($user, $timestamp, $auth);
207
		$token = $this->startNewSession($user, $expiryDate);
208
209
		$currentTimeFormated = \date('c', $currentTime);
210
		$expiryDateFormated = \date('c', $expiryDate);
211
212
		return $this->ampacheResponse([
213
			'auth' => $token,
214
			'version' => self::API_VERSION,
215
			'update' => $currentTimeFormated,
216
			'add' => $currentTimeFormated,
217
			'clean' => $currentTimeFormated,
218
			'songs' => $this->trackBusinessLayer->count($user),
219
			'artists' => $this->artistBusinessLayer->count($user),
220
			'albums' => $this->albumBusinessLayer->count($user),
221
			'playlists' => $this->playlistBusinessLayer->count($user) + 1, // +1 for "All tracks"
222
			'session_expire' => $expiryDateFormated,
223
			'tags' => $this->genreBusinessLayer->count($user),
224
			'videos' => 0
225
		]);
226
	}
227
228
	protected function ping($auth) {
229
		if ($auth !== null && $auth !== '') {
230
			$this->ampacheSessionMapper->extend($auth, \time() + self::SESSION_EXPIRY_TIME);
231
		}
232
233
		return $this->ampacheResponse([
234
			'version' => self::API_VERSION
235
		]);
236
	}
237
238
	protected function artists($filter, $exact, $limit, $offset) {
239
		$artists = $this->findEntities($this->artistBusinessLayer, $filter, $exact, $limit, $offset);
240
		return $this->renderArtists($artists);
241
	}
242
243
	protected function artist($artistId) {
244
		$userId = $this->ampacheUser->getUserId();
245
		$artist = $this->artistBusinessLayer->find($artistId, $userId);
246
		return $this->renderArtists([$artist]);
247
	}
248
249
	protected function artist_albums($artistId, $auth) {
250
		$userId = $this->ampacheUser->getUserId();
251
		$albums = $this->albumBusinessLayer->findAllByArtist($artistId, $userId);
252
		return $this->renderAlbums($albums, $auth);
253
	}
254
255
	protected function artist_songs($artistId, $auth) {
256
		$userId = $this->ampacheUser->getUserId();
257
		$artist = $this->artistBusinessLayer->find($artistId, $userId);
258
		$tracks = $this->trackBusinessLayer->findAllByArtist($artistId, $userId);
259
		$this->injectArtistAndAlbum($tracks, $artist);
260
		return $this->renderSongs($tracks, $auth);
261
	}
262
263
	protected function album_songs($albumId, $auth) {
264
		$userId = $this->ampacheUser->getUserId();
265
266
		$album = $this->albumBusinessLayer->find($albumId, $userId);
267
		$album->setAlbumArtist($this->artistBusinessLayer->find($album->getAlbumArtistId(), $userId));
268
269
		$tracks = $this->trackBusinessLayer->findAllByAlbum($albumId, $userId);
270
		$this->injectArtistAndAlbum($tracks, null, $album);
271
272
		return $this->renderSongs($tracks, $auth);
273
	}
274
275
	protected function song($trackId, $auth) {
276
		$userId = $this->ampacheUser->getUserId();
277
		$track = $this->trackBusinessLayer->find($trackId, $userId);
278
		$trackInArray = [$track];
279
		$this->injectArtistAndAlbum($trackInArray);
280
		return $this->renderSongs($trackInArray, $auth);
281
	}
282
283
	protected function songs($filter, $exact, $limit, $offset, $auth) {
284
285
		// optimized handling for fetching the whole library
286
		// note: the ordering of the songs differs between these two cases
287
		if (empty($filter) && !$limit && !$offset) {
288
			$tracks = $this->getAllTracks();
289
		}
290
		// general case
291
		else {
292
			$tracks = $this->findEntities($this->trackBusinessLayer, $filter, $exact, $limit, $offset);
293
			$this->injectArtistAndAlbum($tracks);
294
		}
295
296
		return $this->renderSongs($tracks, $auth);
297
	}
298
299
	protected function search_songs($filter, $auth) {
300
		$userId = $this->ampacheUser->getUserId();
301
		$tracks = $this->trackBusinessLayer->findAllByNameRecursive($filter, $userId);
302
		$this->injectArtistAndAlbum($tracks);
303
		return $this->renderSongs($tracks, $auth);
304
	}
305
306
	protected function albums($filter, $exact, $limit, $offset, $auth) {
307
		$albums = $this->findEntities($this->albumBusinessLayer, $filter, $exact, $limit, $offset);
308
		return $this->renderAlbums($albums, $auth);
309
	}
310
311
	protected function album($albumId, $auth) {
312
		$userId = $this->ampacheUser->getUserId();
313
		$album = $this->albumBusinessLayer->find($albumId, $userId);
314
		return $this->renderAlbums([$album], $auth);
315
	}
316
317
	protected function playlists($filter, $exact, $limit, $offset) {
318
		$userId = $this->ampacheUser->getUserId();
319
		$playlists = $this->findEntities($this->playlistBusinessLayer, $filter, $exact, $limit, $offset);
320
321
		// append "All tracks" if not searching by name, and it is not off-limit
322
		if (empty($filter) && ($limit === null || \count($playlists) < $limit)) {
323
			$playlists[] = new AmpacheController_AllTracksPlaylist($userId, $this->trackBusinessLayer, $this->l10n);
324
		}
325
326
		return $this->renderPlaylists($playlists);
327
	}
328
329
	protected function playlist($listId) {
330
		$userId = $this->ampacheUser->getUserId();
331
		if ($listId == self::ALL_TRACKS_PLAYLIST_ID) {
332
			$playlist = new AmpacheController_AllTracksPlaylist($userId, $this->trackBusinessLayer, $this->l10n);
333
		} else {
334
			$playlist = $this->playlistBusinessLayer->find($listId, $userId);
335
		}
336
		return $this->renderPlaylists([$playlist]);
337
	}
338
339
	protected function playlist_songs($listId, $limit, $offset, $auth) {
340
		if ($listId == self::ALL_TRACKS_PLAYLIST_ID) {
341
			$playlistTracks = $this->getAllTracks();
342
			$playlistTracks = \array_slice($playlistTracks, $offset, $limit);
343
		}
344
		else {
345
			$userId = $this->ampacheUser->getUserId();
346
			$playlistTracks = $this->playlistBusinessLayer->getPlaylistTracks($listId, $userId, $limit, $offset);
347
			$this->injectArtistAndAlbum($playlistTracks);
348
		}
349
		return $this->renderSongs($playlistTracks, $auth);
350
	}
351
352
	protected function tags($filter, $exact, $limit, $offset) {
353
		$userId = $this->ampacheUser->getUserId();
354
		// TODO: $filter, $exact
355
		$genres = $this->genreBusinessLayer->findAllWithCounts($userId, $limit, $offset);
356
		return $this->renderTags($genres);
357
	}
358
359
	protected function tag($tagId) {
360
		$userId = $this->ampacheUser->getUserId();
361
		$genre = $this->genreBusinessLayer->find($tagId, $userId);
362
		return $this->renderTags([$genre]);
363
	}
364
365
	protected function tag_artists($genreId, $limit, $offset) {
366
		$userId = $this->ampacheUser->getUserId();
367
		$artists = $this->artistBusinessLayer->findAllByGenre($genreId, $userId, $limit, $offset);
368
		return $this->renderArtists($artists);
369
	}
370
371
	protected function tag_albums($genreId, $limit, $offset, $auth) {
372
		$userId = $this->ampacheUser->getUserId();
373
		$albums = $this->albumBusinessLayer->findAllByGenre($genreId, $userId, $limit, $offset);
374
		return $this->renderAlbums($albums, $auth);
375
	}
376
377
	protected function tag_songs($genreId, $limit, $offset, $auth) {
378
		$userId = $this->ampacheUser->getUserId();
379
		$tracks = $this->trackBusinessLayer->findAllByGenre($genreId, $userId, $limit, $offset);
380
		$this->injectArtistAndAlbum($tracks, $artist);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $artist seems to be never defined.
Loading history...
381
		return $this->renderSongs($tracks, $auth);
382
	}
383
384
	protected function play($trackId) {
385
		$userId = $this->ampacheUser->getUserId();
386
387
		try {
388
			$track = $this->trackBusinessLayer->find($trackId, $userId);
389
		} catch (BusinessLayerException $e) {
390
			return new ErrorResponse(Http::STATUS_NOT_FOUND, $e->getMessage());
0 ignored issues
show
Bug introduced by
The type OCA\Music\Controller\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...
391
		}
392
393
		$files = $this->rootFolder->getUserFolder($userId)->getById($track->getFileId());
394
395
		if (\count($files) === 1) {
396
			return new FileResponse($files[0]);
397
		} else {
398
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
399
		}
400
	}
401
402
	/* this is not ampache proto */
403
	protected function get_cover($albumId) {
404
		$userId = $this->ampacheUser->getUserId();
405
		$userFolder = $this->rootFolder->getUserFolder($userId);
406
407
		try {
408
			$coverData = $this->coverHelper->getCover($albumId, $userId, $userFolder);
409
			if ($coverData !== null) {
410
				return new FileResponse($coverData);
411
			}
412
		} catch (BusinessLayerException $e) {
413
			return new ErrorResponse(Http::STATUS_NOT_FOUND, 'album not found');
414
		}
415
416
		return new ErrorResponse(Http::STATUS_NOT_FOUND, 'album has no cover');
417
	}
418
419
420
	/********************
421
	 * Helper functions *
422
	 ********************/
423
424
	private function checkHandshakeTimestamp($timestamp, $currentTime) {
425
		$providedTime = \intval($timestamp);
426
427
		if ($providedTime === 0) {
428
			throw new AmpacheException('Invalid Login - cannot parse time', 401);
429
		}
430
		if ($providedTime < ($currentTime - self::SESSION_EXPIRY_TIME)) {
431
			throw new AmpacheException('Invalid Login - session is outdated', 401);
432
		}
433
		// Allow the timestamp to be at maximum 10 minutes in the future. The client may use its
434
		// own system clock to generate the timestamp and that may differ from the server's time.
435
		if ($providedTime > $currentTime + 600) {
436
			throw new AmpacheException('Invalid Login - timestamp is in future', 401);
437
		}
438
	}
439
440
	private function checkHandshakeAuthentication($user, $timestamp, $auth) {
441
		$hashes = $this->ampacheUserMapper->getPasswordHashes($user);
442
443
		foreach ($hashes as $hash) {
444
			$expectedHash = \hash('sha256', $timestamp . $hash);
445
446
			if ($expectedHash === $auth) {
447
				return;
448
			}
449
		}
450
451
		throw new AmpacheException('Invalid Login - passphrase does not match', 401);
452
	}
453
454
	private function startNewSession($user, $expiryDate) {
455
		// this can cause collision, but it's just a temporary token
456
		$token = \md5(\uniqid(\rand(), true));
457
458
		// create new session
459
		$session = new AmpacheSession();
460
		$session->setUserId($user);
461
		$session->setToken($token);
462
		$session->setExpiry($expiryDate);
463
464
		// save session
465
		$this->ampacheSessionMapper->insert($session);
466
467
		return $token;
468
	}
469
470
	private function findEntities(BusinessLayer $businessLayer, $filter, $exact, $limit=null, $offset=null) {
471
		$userId = $this->ampacheUser->getUserId();
472
473
		if ($filter) {
474
			$fuzzy = !((boolean) $exact);
475
			return $businessLayer->findAllByName($filter, $userId, $fuzzy, $limit, $offset);
476
		} else {
477
			return $businessLayer->findAll($userId, SortBy::Name, $limit, $offset);
478
		}
479
	}
480
481
	/**
482
	 * Getting all tracks with this helper is more efficient than with `findEntities`
483
	 * followed by `injectArtistAndAlbum`. This is because, under the hood, the albums
484
	 * and artists are fetched with a single DB query instead of fetching each separately.
485
	 * 
486
	 * The result set is ordered first by artist and then by song title.
487
	 */
488
	private function getAllTracks() {
489
		$userId = $this->ampacheUser->getUserId();
490
		$tracks = $this->library->getTracksAlbumsAndArtists($userId)['tracks'];
491
		\usort($tracks, ['\OCA\Music\Db\Track', 'compareArtistAndTitle']);
492
		foreach ($tracks as $index => &$track) {
493
			$track->setNumberOnPlaylist($index + 1);
494
		}
495
		return $tracks;
496
	}
497
498
	private function createAmpacheActionUrl($action, $filter, $auth) {
499
		$api = $this->jsonMode ? 'music.ampache.jsonApi' : 'music.ampache.xmlApi';
500
		return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute($api))
501
				. "?action=$action&filter=$filter&auth=$auth";
502
	}
503
504
	private function createAlbumCoverUrl($album, $auth) {
505
		if ($album->getCoverFileId()) {
506
			return $this->createAmpacheActionUrl('_get_cover', $album->getId(), $auth);
507
		} else {
508
			return '';
509
		}
510
	}
511
512
	/**
513
	 * Any non-integer values and integer value 0 are converted to null to
514
	 * indicate "no limit" or "no offset".
515
	 * @param string $value
516
	 * @return integer|null
517
	 */
518
	private static function validateLimitOrOffset($value) {
519
		if (\ctype_digit(\strval($value)) && $value !== 0) {
520
			return \intval($value);
521
		} else {
522
			return null;
523
		}
524
	}
525
526
	private function renderArtists($artists) {
527
		$userId = $this->ampacheUser->getUserId();
528
		$genreMap = Util::createIdLookupTable($this->genreBusinessLayer->findAll($userId));
529
530
		return $this->ampacheResponse([
531
			'total_count' => \count($artists),
532
			'artist' => \array_map(function($artist) use ($userId, $genreMap) {
533
				return [
534
					'id' => $artist->getId(),
535
					'name' => $artist->getNameString($this->l10n),
536
					'albums' => $this->albumBusinessLayer->countByArtist($artist->getId()),
537
					'songs' => $this->trackBusinessLayer->countByArtist($artist->getId()),
538
					'rating' => 0,
539
					'preciserating' => 0,
540
					'tag' => \array_map(function($genreId) use ($genreMap) {
541
						return [
542
							'id' => $genreId,
543
							'value' => $genreMap[$genreId]->getNameString($this->l10n),
544
							'count' => 1
545
						];
546
					}, $this->trackBusinessLayer->getGenresByArtistId($artist->getId(), $userId))
547
				];
548
			}, $artists)
549
		]);
550
	}
551
552
	private function renderAlbums($albums, $auth) {
553
		$userId = $this->ampacheUser->getUserId();
554
555
		$genreMap = Util::createIdLookupTable($this->genreBusinessLayer->findAll($userId));
556
557
		return $this->ampacheResponse([
558
			'total_count' => \count($albums),
559
			'album' => \array_map(function($album) use ($userId, $auth, $genreMap) {
560
				$artist = $this->artistBusinessLayer->find($album->getAlbumArtistId(), $userId);
561
				return [
562
					'id' => $album->getId(),
563
					'name' => $album->getNameString($this->l10n),
564
					'artist' => [
565
						'id' => $artist->getId(),
566
						'value' => $artist->getNameString($this->l10n)
567
					],
568
					'tracks' => $this->trackBusinessLayer->countByAlbum($album->getId()),
569
					'rating' => 0,
570
					'year' => $album->yearToAPI(),
571
					'art' => $this->createAlbumCoverUrl($album, $auth),
572
					'preciserating' => 0,
573
					'tag' => \array_map(function($genreId) use ($genreMap) {
574
						return [
575
							'id' => $genreId,
576
							'value' => $genreMap[$genreId]->getNameString($this->l10n),
577
							'count' => 1
578
						];
579
					}, $album->getGenres())
580
				];
581
			}, $albums)
582
		]);
583
	}
584
585
	private function injectArtistAndAlbum(&$tracks, $commonArtist=null, $commonAlbum=null) {
586
		$userId = $this->ampacheUser->getUserId();
587
588
		foreach ($tracks as &$track) {
589
			$artist = $commonArtist ?: $this->artistBusinessLayer->find($track->getArtistId(), $userId);
590
			$track->setArtist($artist);
591
592
			if (!empty($commonAlbum)) {
593
				$track->setAlbum($commonAlbum);
594
			} else {
595
				$album = $this->albumBusinessLayer->find($track->getAlbumId(), $userId);
596
				$album->setAlbumArtist($this->artistBusinessLayer->find($album->getAlbumArtistId(), $userId));
597
				$track->setAlbum($album);
598
			}
599
		}
600
	}
601
602
	private function renderSongs($tracks, $auth) {
603
		$userId = $this->ampacheUser->getUserId();
604
		$genreMap = Util::createIdLookupTable($this->genreBusinessLayer->findAll($userId));
605
606
		return $this->ampacheResponse([
607
			'total_count' => \count($tracks),
608
			'song' => \array_map(function($track) use ($auth, $genreMap) {
609
				$artist = $track->getArtist();
610
				$album = $track->getAlbum();
611
				$albumArtist = $album->getAlbumArtist();
612
613
				$result = [
614
					'id' => $track->getId(),
615
					'title' => $track->getTitle(),
616
					'artist' => [
617
						'id' => $artist->getId(),
618
						'value' => $artist->getNameString($this->l10n)
619
					],
620
					'albumartist' => [
621
						'id' => $albumArtist->getId(),
622
						'value' => $albumArtist->getNameString($this->l10n)
623
					],
624
					'album' => [
625
						'id' => $album->getId(),
626
						'value' => $album->getNameString($this->l10n)
627
					],
628
					'url' => $this->createAmpacheActionUrl('play', $track->getId(), $auth),
629
					'time' => $track->getLength(),
630
					'year' => $track->getYear(),
631
					'track' => $track->getAdjustedTrackNumber(),
632
					'bitrate' => $track->getBitrate(),
633
					'mime' => $track->getMimetype(),
634
					'size' => $track->getSize(),
635
					'art' => $this->createAlbumCoverUrl($album, $auth),
636
					'rating' => 0,
637
					'preciserating' => 0,
638
				];
639
640
				$genreId = $track->getGenreId();
641
				if ($genreId !== null) {
642
					$result['tag'] = [
643
						'id' => $genreId,
644
						'value' => $genreMap[$genreId]->getNameString($this->l10n),
645
						'count' => 1
646
					];
647
				}
648
				return $result;
649
			}, $tracks)
650
		]);
651
	}
652
653
	private function renderPlaylists($playlists) {
654
		return $this->ampacheResponse([
655
			'total_count' => [\count($playlists)],
656
			'playlist' => \array_map(function($playlist) {
657
				return [
658
					'id' => $playlist->getId(),
659
					'name' => $playlist->getName(),
660
					'owner' => $this->ampacheUser->getUserId(),
661
					'items' => $playlist->getTrackCount(),
662
					'type' => 'Private'
663
				];
664
			}, $playlists)
665
		]);
666
	}
667
668
	private function renderTags($genres) {
669
		return $this->ampacheResponse([
670
			'total_count' => [\count($genres)],
671
			'tag' => \array_map(function($genre) {
672
				return [
673
					'id' => $genre->getId(),
674
					'name' => $genre->getNameString($this->l10n),
675
					'albums' => $genre->getAlbumCount(),
676
					'artists' => $genre->getArtistCount(),
677
					'songs' => $genre->getTrackCount(),
678
					'videos' => 0,
679
					'playlists' => 0,
680
					'stream' => 0
681
				];
682
			}, $genres)
683
		]);
684
	}
685
686
}
687
688
/**
689
 * Adapter class which acts like the Playlist class for the purpose of 
690
 * AmpacheController::renderPlaylists but contains all the track of the user. 
691
 */
692
class AmpacheController_AllTracksPlaylist {
693
694
	private $user;
695
	private $trackBusinessLayer;
696
	private $l10n;
697
698
	public function __construct($user, $trackBusinessLayer, $l10n) {
699
		$this->user = $user;
700
		$this->trackBusinessLayer = $trackBusinessLayer;
701
		$this->l10n = $l10n;
702
	}
703
704
	public function getId() {
705
		return AmpacheController::ALL_TRACKS_PLAYLIST_ID;
706
	}
707
708
	public function getName() {
709
		return $this->l10n->t('All tracks');
710
	}
711
712
	public function getTrackCount() {
713
		return $this->trackBusinessLayer->count($this->user);
714
	}
715
}
716