Completed
Push — feature/464_small_player_for_f... ( 144ef9...383208 )
by Pauli
11:38
created

ApiController::fileInfo()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 13
rs 9.4285
cc 2
eloc 9
nc 2
nop 1
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
 * @copyright Morris Jobke 2013, 2014
11
 */
12
13
namespace OCA\Music\Controller;
14
15
use OCA\Music\AppFramework\Core\Logger;
16
use OCA\Music\Db\Artist;
17
use OCA\Music\Db\Cache;
18
use OCA\Music\Db\Track;
19
use \OCP\AppFramework\Controller;
20
use \OCP\AppFramework\Http;
21
use \OCP\AppFramework\Http\DataDisplayResponse;
22
use \OCP\AppFramework\Http\JSONResponse;
23
use \OCP\AppFramework\Http\Response;
24
use \OCP\Files\Folder;
25
use \OCP\IL10N;
26
use \OCP\IRequest;
27
use \OCP\IURLGenerator;
28
29
use \OCP\AppFramework\Db\DoesNotExistException;
30
31
use \OCA\Music\BusinessLayer\TrackBusinessLayer;
32
use \OCA\Music\BusinessLayer\ArtistBusinessLayer;
33
use \OCA\Music\BusinessLayer\AlbumBusinessLayer;
34
use \OCA\Music\Http\FileResponse;
35
use \OCA\Music\Utility\Scanner;
36
use \OCA\Music\Utility\CoverHelper;
37
38
39
class ApiController extends Controller {
40
41
	/** @var IL10N */
42
	private $l10n;
43
	/** @var TrackBusinessLayer */
44
	private $trackBusinessLayer;
45
	/** @var ArtistBusinessLayer */
46
	private $artistBusinessLayer;
47
	/** @var AlbumBusinessLayer */
48
	private $albumBusinessLayer;
49
	/** @var Cache */
50
	private $cache;
51
	/** @var Scanner */
52
	private $scanner;
53
	/** @var CoverHelper */
54
	private $coverHelper;
55
	/** @var string */
56
	private $userId;
57
	/** @var IURLGenerator */
58
	private $urlGenerator;
59
	/** @var Folder */
60
	private $userFolder;
61
	/** @var Logger */
62
	private $logger;
63
64
	public function __construct($appname,
65
								IRequest $request,
66
								IURLGenerator $urlGenerator,
67
								TrackBusinessLayer $trackbusinesslayer,
68
								ArtistBusinessLayer $artistbusinesslayer,
69
								AlbumBusinessLayer $albumbusinesslayer,
70
								Cache $cache,
71
								Scanner $scanner,
72
								CoverHelper $coverHelper,
73
								$userId,
74
								$l10n,
75
								Folder $userFolder,
76
								Logger $logger){
77
		parent::__construct($appname, $request);
78
		$this->l10n = $l10n;
79
		$this->trackBusinessLayer = $trackbusinesslayer;
80
		$this->artistBusinessLayer = $artistbusinesslayer;
81
		$this->albumBusinessLayer = $albumbusinesslayer;
82
		$this->cache = $cache;
83
		$this->scanner = $scanner;
84
		$this->coverHelper = $coverHelper;
85
		$this->userId = $userId;
86
		$this->urlGenerator = $urlGenerator;
87
		$this->userFolder = $userFolder;
88
		$this->logger = $logger;
89
	}
90
91
	/**
92
	 * Extracts the id from an unique slug (id-slug)
93
	 * @param string $slug the slug
94
	 * @return string the id
95
	 */
96
	protected function getIdFromSlug($slug){
97
		$split = explode('-', $slug, 2);
98
99
		return $split[0];
100
	}
101
102
	/**
103
	 * @NoAdminRequired
104
	 * @NoCSRFRequired
105
	 */
106
	public function collection() {
107
		$collectionJson = $this->cache->get($this->userId, 'collection');
108
109
		if ($collectionJson === null) {
110
			$collectionJson = $this->buildCollectionJson();
111
			$this->cache->add($this->userId, 'collection', $collectionJson);
112
		}
113
114
		$response = new DataDisplayResponse($collectionJson);
115
		$response->addHeader('Content-Type', 'application/json; charset=utf-8');
116
		return $response;
117
	}
118
119
	/**
120
	 * Small cached images are embedded directly to the collection to limit the number of PHP queries.
121
	 * However, the total size of all embedded covers is limited to avoid the size of the collection
122
	 * from getting out of control with large music libraries.
123
	 * @param int $albumId
124
	 * @return string|null
125
	 */
126
	private function coverToEmbed($albumId) {
127
		$cover = $this->coverHelper->getCoverFromCache($albumId, $this->userId, true);
128
		if ($cover != null) {
129
			$size = strlen($cover['content']);
130
			if ($size + $this->embeddedCoversTotalSize > self::EMBEDDED_COVERS_MAX_TOTAL_SIZE) {
131
				$cover = null;
132
			} else {
133
				$this->embeddedCoversTotalSize += $size;
134
			}
135
		}
136
		return $cover;
137
	}
138
	const EMBEDDED_COVERS_MAX_TOTAL_SIZE = 3145728; // 3 MB
139
	private $embeddedCoversTotalSize = 0;
140
141
	private function buildCollectionJson() {
142
		/** @var Artist[] $allArtists */
143
		$allArtists = $this->artistBusinessLayer->findAll($this->userId);
144
		$allArtistsByIdAsObj = array();
145
		$allArtistsByIdAsArr = array();
146
		foreach ($allArtists as &$artist) {
147
			$artistId = $artist->getId();
148
			$allArtistsByIdAsObj[$artistId] = $artist;
149
			$allArtistsByIdAsArr[$artistId] = $artist->toCollection($this->l10n);
150
		}
151
152
		$allAlbums = $this->albumBusinessLayer->findAll($this->userId);
153
		$allAlbumsByIdAsObj = array();
154
		$allAlbumsByIdAsArr = array();
155
		foreach ($allAlbums as &$album) {
156
			$albumId = $album->getId();
157
			$allAlbumsByIdAsObj[$albumId] = $album;
158
			$allAlbumsByIdAsArr[$albumId] = $album->toCollection(
159
					$this->urlGenerator, $this->l10n, $this->coverToEmbed($albumId));
160
		}
161
162
		/** @var Track[] $allTracks */
163
		$allTracks = $this->trackBusinessLayer->findAll($this->userId);
164
165
		$artists = array();
166
		foreach ($allTracks as $track) {
167
			$albumObj = $allAlbumsByIdAsObj[$track->getAlbumId()];
168
			$trackArtistObj = $allArtistsByIdAsObj[$track->getArtistId()];
169
			$track->setAlbum($albumObj);
170
			$track->setArtist($trackArtistObj);
171
172
			$albumArtist = &$allArtistsByIdAsArr[$albumObj->getAlbumArtistId()];
173
			if (!isset($albumArtist['albums'])) {
174
				$albumArtist['albums'] = array();
175
				$artists[] = &$albumArtist;
176
			}
177
			$album = &$allAlbumsByIdAsArr[$track->getAlbumId()];
178
			if (!isset($album['tracks'])) {
179
				$album['tracks'] = array();
180
				$albumArtist['albums'][] = &$album;
181
			}
182
			try {
183
				$album['tracks'][] = $track->toCollection($this->l10n);
184
			} catch (\OCP\Files\NotFoundException $e) {
185
				//ignore not found
186
			}
187
		}
188
		return json_encode($artists);
189
	}
190
191
	/**
192
	 * @NoAdminRequired
193
	 * @NoCSRFRequired
194
	 */
195
	public function artists($fulltree, $albums) {
196
		$fulltree = filter_var($fulltree, FILTER_VALIDATE_BOOLEAN);
197
		$includeAlbums = filter_var($albums, FILTER_VALIDATE_BOOLEAN);
198
		/** @var Artist[] $artists */
199
		$artists = $this->artistBusinessLayer->findAll($this->userId);
200
		foreach($artists as &$artist) {
201
			$artist = $artist->toAPI($this->urlGenerator, $this->l10n);
202
			if($fulltree || $includeAlbums) {
203
				$artistId = $artist['id'];
204
				$artistAlbums = $this->albumBusinessLayer->findAllByArtist($artistId, $this->userId);
205
				foreach($artistAlbums as &$album) {
206
					$album = $album->toAPI($this->urlGenerator, $this->l10n);
207
					if($fulltree) {
208
						$albumId = $album['id'];
209
						$tracks = $this->trackBusinessLayer->findAllByAlbum($albumId, $this->userId, $artistId);
210
						foreach($tracks as &$track) {
211
							$track = $track->toAPI($this->urlGenerator);
212
						}
213
						$album['tracks'] = $tracks;
214
					}
215
				}
216
				$artist['albums'] = $artistAlbums;
217
			}
218
		}
219
		return new JSONResponse($artists);
220
	}
221
222
	/**
223
	 * @NoAdminRequired
224
	 * @NoCSRFRequired
225
	 */
226 View Code Duplication
	public function artist($artistIdOrSlug, $fulltree) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
227
		$fulltree = filter_var($fulltree, FILTER_VALIDATE_BOOLEAN);
228
		$artistId = $this->getIdFromSlug($artistIdOrSlug);
229
		/** @var Artist $artist */
230
		$artist = $this->artistBusinessLayer->find($artistId, $this->userId);
231
		$artist = $artist->toAPI($this->urlGenerator, $this->l10n);
232
		if($fulltree) {
233
			$artistId = $artist['id'];
234
			$albums = $this->albumBusinessLayer->findAllByArtist($artistId, $this->userId);
235
			foreach($albums as &$album) {
236
				$album = $album->toAPI($this->urlGenerator, $this->l10n);
237
				$albumId = $album['id'];
238
				$tracks = $this->trackBusinessLayer->findAllByAlbum($albumId, $this->userId, $artistId);
239
				foreach($tracks as &$track) {
240
					$track = $track->toAPI($this->urlGenerator);
241
				}
242
				$album['tracks'] = $tracks;
243
			}
244
			$artist['albums'] = $albums;
245
		}
246
		return new JSONResponse($artist);
247
	}
248
249
	/**
250
	 * @NoAdminRequired
251
	 * @NoCSRFRequired
252
	 */
253 View Code Duplication
	public function albums($fulltree) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
254
		$fulltree = filter_var($fulltree, FILTER_VALIDATE_BOOLEAN);
255
		$albums = $this->albumBusinessLayer->findAll($this->userId);
256
		foreach($albums as &$album) {
257
			$artistIds = $album->getArtistIds();
258
			$album = $album->toAPI($this->urlGenerator, $this->l10n);
259
			if($fulltree) {
260
				$albumId = $album['id'];
261
				$tracks = $this->trackBusinessLayer->findAllByAlbum($albumId, $this->userId);
262
				foreach($tracks as &$track) {
263
					$track = $track->toAPI($this->urlGenerator);
264
				}
265
				$album['tracks'] = $tracks;
266
				$artists = $this->artistBusinessLayer->findMultipleById($artistIds, $this->userId);
267
				foreach($artists as &$artist) {
268
					$artist = $artist->toAPI($this->urlGenerator, $this->l10n);
269
				}
270
				$album['artists'] = $artists;
271
			}
272
		}
273
		return new JSONResponse($albums);
274
	}
275
276
	/**
277
	 * @NoAdminRequired
278
	 * @NoCSRFRequired
279
	 */
280 View Code Duplication
	public function album($albumIdOrSlug, $fulltree) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
281
		$fulltree = filter_var($fulltree, FILTER_VALIDATE_BOOLEAN);
282
		$albumId = $this->getIdFromSlug($albumIdOrSlug);
283
		$album = $this->albumBusinessLayer->find($albumId, $this->userId);
284
285
		$artistIds = $album->getArtistIds();
286
		$album = $album->toAPI($this->urlGenerator, $this->l10n);
287
		if($fulltree) {
288
			$albumId = $album['id'];
289
			$tracks = $this->trackBusinessLayer->findAllByAlbum($albumId, $this->userId);
290
			foreach($tracks as &$track) {
291
				$track = $track->toAPI($this->urlGenerator);
292
			}
293
			$album['tracks'] = $tracks;
294
			$artists = $this->artistBusinessLayer->findMultipleById($artistIds, $this->userId);
295
			foreach($artists as &$artist) {
296
				$artist = $artist->toAPI($this->urlGenerator, $this->l10n);
297
			}
298
			$album['artists'] = $artists;
299
		}
300
301
		return new JSONResponse($album);
302
	}
303
304
	/**
305
	 * @NoAdminRequired
306
	 * @NoCSRFRequired
307
	 */
308
	public function tracks($artist, $album, $fulltree) {
309
		$fulltree = filter_var($fulltree, FILTER_VALIDATE_BOOLEAN);
310
		if($artist) {
311
			$tracks = $this->trackBusinessLayer->findAllByArtist($artist, $this->userId);
312
		} elseif($album) {
313
			$tracks = $this->trackBusinessLayer->findAllByAlbum($album, $this->userId);
314
		} else {
315
			$tracks = $this->trackBusinessLayer->findAll($this->userId);
316
		}
317
		foreach($tracks as &$track) {
318
			$artistId = $track->getArtistId();
319
			$albumId = $track->getAlbumId();
320
			$track = $track->toAPI($this->urlGenerator);
321
			if($fulltree) {
322
				/** @var Artist $artist */
323
				$artist = $this->artistBusinessLayer->find($artistId, $this->userId);
324
				$track['artist'] = $artist->toAPI($this->urlGenerator, $this->l10n);
325
				$album = $this->albumBusinessLayer->find($albumId, $this->userId);
326
				$track['album'] = $album->toAPI($this->urlGenerator, $this->l10n);
327
			}
328
		}
329
		return new JSONResponse($tracks);
330
	}
331
332
	/**
333
	 * @NoAdminRequired
334
	 * @NoCSRFRequired
335
	 */
336
	public function track($trackIdOrSlug) {
337
		$trackId = $this->getIdFromSlug($trackIdOrSlug);
338
		/** @var Track $track */
339
		$track = $this->trackBusinessLayer->find($trackId, $this->userId);
340
		return new JSONResponse($track->toAPI($this->urlGenerator));
341
	}
342
343
	/**
344
	 * @NoAdminRequired
345
	 * @NoCSRFRequired
346
	 */
347
	public function trackByFileId($fileId) {
348
		$track = $this->trackBusinessLayer->findByFileId($fileId, $this->userId);
349
		$track->setAlbum($this->albumBusinessLayer->find($track->getAlbumId(), $this->userId));
350
		$track->setArtist($this->artistBusinessLayer->find($track->getArtistId(), $this->userId));
351
		return new JSONResponse($track->toCollection($this->l10n));
352
	}
353
354
	/**
355
	 * @NoAdminRequired
356
	 * @NoCSRFRequired
357
	 */
358
	public function fileWebDavUrl($fileId) {
359
		$nodes = $this->userFolder->getById($fileId);
360
		if (count($nodes) == 0) {
361
			$r = new Response();
362
			$r->setStatus(Http::STATUS_NOT_FOUND);
363
			return $r;
364
		}
365
		else {
366
			$node = $nodes[0];
367
			$relativePath = $this->userFolder->getRelativePath($node->getPath());
368
			$url = $this->urlGenerator->getAbsoluteUrl('remote.php/webdav' . $relativePath);
369
			return new JSONResponse(['url' => $url]);
370
		}
371
	}
372
373
	/**
374
	 * @NoAdminRequired
375
	 */
376
	public function getScanState() {
377
		return new JSONResponse([
378
			'unscannedFiles' => $this->scanner->getUnscannedMusicFileIds($this->userId, $this->userFolder),
379
			'scannedCount' => count($this->scanner->getScannedFiles($this->userId))
380
		]);
381
	}
382
383
	/**
384
	 * @NoAdminRequired
385
	 */
386
	public function scan($files, $finalize) {
387
		// extract the parameters
388
		$fileIds = array_map('intval', explode(',', $files));
389
		$finalize = filter_var($finalize, FILTER_VALIDATE_BOOLEAN);
390
391
		$filesScanned = $this->scanner->scanFiles($this->userId, $this->userFolder, $fileIds);
392
393
		$coversUpdated = false;
394
		if ($finalize) {
395
			$coversUpdated = $this->scanner->findCovers();
396
			$totalCount = count($this->scanner->getScannedFiles($this->userId));
397
			$this->logger->log("Scanning finished, user $this->userId has $totalCount scanned tracks in total", 'info');
398
		}
399
400
		return new JSONResponse([
401
			'filesScanned' => $filesScanned,
402
			'coversUpdated' => $coversUpdated
403
		]);
404
	}
405
406
	/**
407
	 * @NoAdminRequired
408
	 * @NoCSRFRequired
409
	 */
410
	public function download($fileId) {
411
		// we no longer need the session to be kept open
412
		session_write_close();
413
414
		try {
415
			$track = $this->trackBusinessLayer->findByFileId($fileId, $this->userId);
416
		} catch(DoesNotExistException $e) {
417
			$r = new Response();
418
			$r->setStatus(Http::STATUS_NOT_FOUND);
419
			return $r;
420
		}
421
422
		$nodes = $this->userFolder->getById($track->getFileId());
423
		if(count($nodes) > 0 ) {
424
			// get the first valid node
425
			$node = $nodes[0];
426
427
			$mime = $node->getMimeType();
428
			$content = $node->getContent();
429
			return new FileResponse(array('mimetype' => $mime, 'content' => $content));
430
		}
431
432
		$r = new Response();
433
		$r->setStatus(Http::STATUS_NOT_FOUND);
434
		return $r;
435
	}
436
437
	/**
438
	 * @NoAdminRequired
439
	 * @NoCSRFRequired
440
	 */
441
	public function fileInfo($fileId) {
442
		// we no longer need the session to be kept open
443
		session_write_close();
444
445
		$info = $this->scanner->getFileInfo($fileId, $this->userId, $this->userFolder);
446
		if ($info) {
447
			return new JSONResponse($info);
448
		} else {
449
			$r = new Response();
450
			$r->setStatus(Http::STATUS_NOT_FOUND);
451
			return $r;
452
		}
453
	}
454
455
	/**
456
	 * @NoAdminRequired
457
	 * @NoCSRFRequired
458
	 */
459 View Code Duplication
	public function cover($albumIdOrSlug) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
460
		// we no longer need the session to be kept open
461
		session_write_close();
462
463
		$albumId = $this->getIdFromSlug($albumIdOrSlug);
464
		$coverData = $this->coverHelper->getCover($albumId, $this->userId, $this->userFolder);
465
466
		if ($coverData !== NULL) {
467
			return new FileResponse($coverData);
468
		} else {
469
			$r = new Response();
470
			$r->setStatus(Http::STATUS_NOT_FOUND);
471
			return $r;
472
		}
473
	}
474
}
475