Passed
Push — master ( f0b4ab...0b391b )
by Pauli
04:06
created

AmpacheController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 37
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 15
nc 1
nop 16
dl 0
loc 37
ccs 0
cts 16
cp 0
crap 2
rs 9.7666
c 0
b 0
f 0

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