Completed
Push — feature/track_details ( 60378c )
by Pauli
14:35
created

ApiController::fileDetails()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
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
 * @author Pauli Järvinen <[email protected]>
11
 * @copyright Morris Jobke 2013, 2014
12
 * @copyright Pauli Järvinen 2017, 2018
13
 */
14
15
namespace OCA\Music\Controller;
16
17
use \OCP\AppFramework\Controller;
18
use \OCP\AppFramework\Http;
19
use \OCP\AppFramework\Http\DataDisplayResponse;
20
use \OCP\AppFramework\Http\JSONResponse;
21
use \OCP\Files\Folder;
22
use \OCP\IL10N;
23
use \OCP\IRequest;
24
use \OCP\IURLGenerator;
25
26
use \OCA\Music\AppFramework\Core\Logger;
27
use \OCA\Music\BusinessLayer\AlbumBusinessLayer;
28
use \OCA\Music\BusinessLayer\ArtistBusinessLayer;
29
use \OCA\Music\BusinessLayer\TrackBusinessLayer;
30
use \OCA\Music\Db\Artist;
31
use \OCA\Music\Db\Maintenance;
32
use \OCA\Music\Db\Track;
33
use \OCA\Music\Http\ErrorResponse;
34
use \OCA\Music\Http\FileResponse;
35
use \OCA\Music\Utility\CollectionHelper;
36
use \OCA\Music\Utility\CoverHelper;
37
use \OCA\Music\Utility\DetailsHelper;
38
use \OCA\Music\Utility\Scanner;
39
40
class ApiController extends Controller {
41
42
	/** @var IL10N */
43
	private $l10n;
44
	/** @var TrackBusinessLayer */
45
	private $trackBusinessLayer;
46
	/** @var ArtistBusinessLayer */
47
	private $artistBusinessLayer;
48
	/** @var AlbumBusinessLayer */
49
	private $albumBusinessLayer;
50
	/** @var Scanner */
51
	private $scanner;
52
	/** @var CollectionHelper */
53
	private $collectionHelper;
54
	/** @var CoverHelper */
55
	private $coverHelper;
56
	/** @var DetailsHelper */
57
	private $detailsHelper;
58
	/** @var Maintenance */
59
	private $maintenance;
60
	/** @var string */
61
	private $userId;
62
	/** @var IURLGenerator */
63
	private $urlGenerator;
64
	/** @var Folder */
65
	private $userFolder;
66
	/** @var Logger */
67
	private $logger;
68
69 View Code Duplication
	public function __construct($appname,
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...
70
								IRequest $request,
71
								IURLGenerator $urlGenerator,
72
								TrackBusinessLayer $trackbusinesslayer,
73
								ArtistBusinessLayer $artistbusinesslayer,
74
								AlbumBusinessLayer $albumbusinesslayer,
75
								Scanner $scanner,
76
								CollectionHelper $collectionHelper,
77
								CoverHelper $coverHelper,
78
								DetailsHelper $detailsHelper,
79
								Maintenance $maintenance,
80
								$userId,
81
								IL10N $l10n,
82
								Folder $userFolder,
83
								Logger $logger) {
84
		parent::__construct($appname, $request);
85
		$this->l10n = $l10n;
86
		$this->trackBusinessLayer = $trackbusinesslayer;
87
		$this->artistBusinessLayer = $artistbusinesslayer;
88
		$this->albumBusinessLayer = $albumbusinesslayer;
89
		$this->scanner = $scanner;
90
		$this->collectionHelper = $collectionHelper;
91
		$this->coverHelper = $coverHelper;
92
		$this->detailsHelper = $detailsHelper;
93
		$this->maintenance = $maintenance;
94
		$this->userId = $userId;
95
		$this->urlGenerator = $urlGenerator;
96
		$this->userFolder = $userFolder;
97
		$this->logger = $logger;
98
	}
99
100
	/**
101
	 * Extracts the id from an unique slug (id-slug)
102
	 * @param string $slug the slug
103
	 * @return string the id
104
	 */
105
	protected function getIdFromSlug($slug) {
106
		$split = \explode('-', $slug, 2);
107
108
		return $split[0];
109
	}
110
111
	/**
112
	 * @NoAdminRequired
113
	 * @NoCSRFRequired
114
	 */
115
	public function prepareCollection() {
116
		$hash = $this->collectionHelper->getCachedJsonHash();
117
		if ($hash === null) {
118
			// build the collection but ignore the data for now
119
			$this->collectionHelper->getJson();
120
			$hash = $this->collectionHelper->getCachedJsonHash();
121
		}
122
		return new JSONResponse(['hash' => $hash]);
123
	}
124
125
	/**
126
	 * @NoAdminRequired
127
	 * @NoCSRFRequired
128
	 */
129
	public function collection() {
130
131
		$collectionJson = $this->collectionHelper->getJson();
132
		$response = new DataDisplayResponse($collectionJson);
133
		$response->addHeader('Content-Type', 'application/json; charset=utf-8');
134
135
		// Instruct the client to cache the result in case it requested the collection with
136
		// the correct hash. The hash could be incorrect if the collection would have changed
137
		// between calls to prepareCollection() and colletion().
138
		$requestHash = $this->request->getParam('hash');
139
		$actualHash = $this->collectionHelper->getCachedJsonHash();
140
		if (!empty($actualHash) && $requestHash === $actualHash) {
141
			self::setClientCaching($response, 90); // cache for 3 months
142
		}
143
144
		return $response;
145
	}
146
147
	/**
148
	 * @NoAdminRequired
149
	 * @NoCSRFRequired
150
	 */
151
	public function artists($fulltree, $albums) {
152
		$fulltree = \filter_var($fulltree, FILTER_VALIDATE_BOOLEAN);
153
		$includeAlbums = \filter_var($albums, FILTER_VALIDATE_BOOLEAN);
154
		/** @var Artist[] $artists */
155
		$artists = $this->artistBusinessLayer->findAll($this->userId);
156
		foreach ($artists as &$artist) {
157
			$artist = $artist->toAPI($this->urlGenerator, $this->l10n);
158
			if ($fulltree || $includeAlbums) {
159
				$artistId = $artist['id'];
160
				$artistAlbums = $this->albumBusinessLayer->findAllByArtist($artistId, $this->userId);
161
				foreach ($artistAlbums as &$album) {
162
					$album = $album->toAPI($this->urlGenerator, $this->l10n);
163
					if ($fulltree) {
164
						$albumId = $album['id'];
165
						$tracks = $this->trackBusinessLayer->findAllByAlbum($albumId, $this->userId, $artistId);
166
						foreach ($tracks as &$track) {
167
							$track = $track->toAPI($this->urlGenerator);
168
						}
169
						$album['tracks'] = $tracks;
170
					}
171
				}
172
				$artist['albums'] = $artistAlbums;
173
			}
174
		}
175
		return new JSONResponse($artists);
176
	}
177
178
	/**
179
	 * @NoAdminRequired
180
	 * @NoCSRFRequired
181
	 */
182 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...
183
		$fulltree = \filter_var($fulltree, FILTER_VALIDATE_BOOLEAN);
184
		$artistId = $this->getIdFromSlug($artistIdOrSlug);
185
		/** @var Artist $artist */
186
		$artist = $this->artistBusinessLayer->find($artistId, $this->userId);
187
		$artist = $artist->toAPI($this->urlGenerator, $this->l10n);
188
		if ($fulltree) {
189
			$artistId = $artist['id'];
190
			$albums = $this->albumBusinessLayer->findAllByArtist($artistId, $this->userId);
191
			foreach ($albums as &$album) {
192
				$album = $album->toAPI($this->urlGenerator, $this->l10n);
193
				$albumId = $album['id'];
194
				$tracks = $this->trackBusinessLayer->findAllByAlbum($albumId, $this->userId, $artistId);
195
				foreach ($tracks as &$track) {
196
					$track = $track->toAPI($this->urlGenerator);
197
				}
198
				$album['tracks'] = $tracks;
199
			}
200
			$artist['albums'] = $albums;
201
		}
202
		return new JSONResponse($artist);
203
	}
204
205
	/**
206
	 * @NoAdminRequired
207
	 * @NoCSRFRequired
208
	 */
209 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...
210
		$fulltree = \filter_var($fulltree, FILTER_VALIDATE_BOOLEAN);
211
		$albums = $this->albumBusinessLayer->findAll($this->userId);
212
		foreach ($albums as &$album) {
213
			$artistIds = $album->getArtistIds();
214
			$album = $album->toAPI($this->urlGenerator, $this->l10n);
215
			if ($fulltree) {
216
				$albumId = $album['id'];
217
				$tracks = $this->trackBusinessLayer->findAllByAlbum($albumId, $this->userId);
218
				foreach ($tracks as &$track) {
219
					$track = $track->toAPI($this->urlGenerator);
220
				}
221
				$album['tracks'] = $tracks;
222
				$artists = $this->artistBusinessLayer->findMultipleById($artistIds, $this->userId);
223
				foreach ($artists as &$artist) {
224
					$artist = $artist->toAPI($this->urlGenerator, $this->l10n);
225
				}
226
				$album['artists'] = $artists;
227
			}
228
		}
229
		return new JSONResponse($albums);
230
	}
231
232
	/**
233
	 * @NoAdminRequired
234
	 * @NoCSRFRequired
235
	 */
236 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...
237
		$fulltree = \filter_var($fulltree, FILTER_VALIDATE_BOOLEAN);
238
		$albumId = $this->getIdFromSlug($albumIdOrSlug);
239
		$album = $this->albumBusinessLayer->find($albumId, $this->userId);
240
241
		$artistIds = $album->getArtistIds();
242
		$album = $album->toAPI($this->urlGenerator, $this->l10n);
243
		if ($fulltree) {
244
			$albumId = $album['id'];
245
			$tracks = $this->trackBusinessLayer->findAllByAlbum($albumId, $this->userId);
246
			foreach ($tracks as &$track) {
247
				$track = $track->toAPI($this->urlGenerator);
248
			}
249
			$album['tracks'] = $tracks;
250
			$artists = $this->artistBusinessLayer->findMultipleById($artistIds, $this->userId);
251
			foreach ($artists as &$artist) {
252
				$artist = $artist->toAPI($this->urlGenerator, $this->l10n);
253
			}
254
			$album['artists'] = $artists;
255
		}
256
257
		return new JSONResponse($album);
258
	}
259
260
	/**
261
	 * @NoAdminRequired
262
	 * @NoCSRFRequired
263
	 */
264
	public function tracks($artist, $album, $fulltree) {
265
		$fulltree = \filter_var($fulltree, FILTER_VALIDATE_BOOLEAN);
266
		if ($artist) {
267
			$tracks = $this->trackBusinessLayer->findAllByArtist($artist, $this->userId);
268
		} elseif ($album) {
269
			$tracks = $this->trackBusinessLayer->findAllByAlbum($album, $this->userId);
270
		} else {
271
			$tracks = $this->trackBusinessLayer->findAll($this->userId);
272
		}
273
		foreach ($tracks as &$track) {
274
			$artistId = $track->getArtistId();
275
			$albumId = $track->getAlbumId();
276
			$track = $track->toAPI($this->urlGenerator);
277
			if ($fulltree) {
278
				/** @var Artist $artist */
279
				$artist = $this->artistBusinessLayer->find($artistId, $this->userId);
280
				$track['artist'] = $artist->toAPI($this->urlGenerator, $this->l10n);
281
				$album = $this->albumBusinessLayer->find($albumId, $this->userId);
282
				$track['album'] = $album->toAPI($this->urlGenerator, $this->l10n);
283
			}
284
		}
285
		return new JSONResponse($tracks);
286
	}
287
288
	/**
289
	 * @NoAdminRequired
290
	 * @NoCSRFRequired
291
	 */
292
	public function track($trackIdOrSlug) {
293
		$trackId = $this->getIdFromSlug($trackIdOrSlug);
294
		/** @var Track $track */
295
		$track = $this->trackBusinessLayer->find($trackId, $this->userId);
296
		return new JSONResponse($track->toAPI($this->urlGenerator));
297
	}
298
299
	/**
300
	 * @NoAdminRequired
301
	 * @NoCSRFRequired
302
	 */
303
	public function trackByFileId($fileId) {
304
		$track = $this->trackBusinessLayer->findByFileId($fileId, $this->userId);
305
		if ($track !== null) {
306
			$track->setAlbum($this->albumBusinessLayer->find($track->getAlbumId(), $this->userId));
307
			$track->setArtist($this->artistBusinessLayer->find($track->getArtistId(), $this->userId));
308
			return new JSONResponse($track->toCollection($this->l10n));
309
		} else {
310
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
311
		}
312
	}
313
314
	/**
315
	 * @NoAdminRequired
316
	 * @NoCSRFRequired
317
	 */
318
	public function filePath($fileId) {
319
		$nodes = $this->userFolder->getById($fileId);
320
		if (\count($nodes) == 0) {
321
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
322
		} else {
323
			$node = $nodes[0];
324
			$path = $this->userFolder->getRelativePath($node->getPath());
325
			// URL encode each part of the file path
326
			$path = \join('/', \array_map('rawurlencode', \explode('/', $path)));
327
			return new JSONResponse(['path' => $path]);
328
		}
329
	}
330
331
	/**
332
	 * @NoAdminRequired
333
	 * @NoCSRFRequired
334
	 * DEPRECATED
335
	 */
336
	public function fileWebDavUrl($fileId) {
337
		$nodes = $this->userFolder->getById($fileId);
338
		if (\count($nodes) == 0) {
339
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
340
		} else {
341
			$node = $nodes[0];
342
			$relativePath = $this->userFolder->getRelativePath($node->getPath());
343
			// URL encode each part of the file path
344
			$relativePath = \join('/', \array_map('rawurlencode', \explode('/', $relativePath)));
345
			$url = $this->urlGenerator->getAbsoluteUrl('remote.php/webdav' . $relativePath);
346
			return new JSONResponse(['url' => $url]);
347
		}
348
	}
349
350
	/**
351
	 * @NoAdminRequired
352
	 */
353
	public function getScanState() {
354
		return new JSONResponse([
355
			'unscannedFiles' => $this->scanner->getUnscannedMusicFileIds($this->userId, $this->userFolder),
356
			'scannedCount' => $this->trackBusinessLayer->count($this->userId)
357
		]);
358
	}
359
360
	/**
361
	 * @NoAdminRequired
362
	 * @UseSession to keep the session reserved while execution in progress
363
	 */
364
	public function scan($files, $finalize) {
365
		// extract the parameters
366
		$fileIds = \array_map('intval', \explode(',', $files));
367
		$finalize = \filter_var($finalize, FILTER_VALIDATE_BOOLEAN);
368
369
		$filesScanned = $this->scanner->scanFiles($this->userId, $this->userFolder, $fileIds);
370
371
		$coversUpdated = false;
372
		if ($finalize) {
373
			$coversUpdated = $this->scanner->findCovers();
374
			$totalCount = \count($this->scanner->getScannedFiles($this->userId));
375
			$this->logger->log("Scanning finished, user $this->userId has $totalCount scanned tracks in total", 'info');
376
		}
377
378
		return new JSONResponse([
379
			'filesScanned' => $filesScanned,
380
			'coversUpdated' => $coversUpdated
381
		]);
382
	}
383
384
	/**
385
	 * @NoAdminRequired
386
	 * @UseSession to keep the session reserved while execution in progress
387
	 */
388
	public function resetScanned() {
389
		$this->maintenance->resetDb($this->userId);
390
		return new JSONResponse(['success' => true]);
391
	}
392
393
	/**
394
	 * @NoAdminRequired
395
	 * @NoCSRFRequired
396
	 */
397
	public function download($fileId) {
398
		$track = $this->trackBusinessLayer->findByFileId($fileId, $this->userId);
399
		if ($track === null) {
400
			return new ErrorResponse(Http::STATUS_NOT_FOUND, 'track not found');
401
		}
402
403
		$nodes = $this->userFolder->getById($track->getFileId());
404
		if (\count($nodes) > 0) {
405
			// get the first valid node
406
			$node = $nodes[0];
407
408
			$mime = $node->getMimeType();
409
			$content = $node->getContent();
410
			return new FileResponse(['mimetype' => $mime, 'content' => $content]);
411
		}
412
413
		return new ErrorResponse(Http::STATUS_NOT_FOUND, 'file not found');
414
	}
415
416
	/**
417
	 * @NoAdminRequired
418
	 * @NoCSRFRequired
419
	 */
420
	public function fileInfo($fileId) {
421
		$info = $this->scanner->getFileInfo($fileId, $this->userId, $this->userFolder);
422
		if ($info) {
423
			return new JSONResponse($info);
424
		} else {
425
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
426
		}
427
	}
428
429
	/**
430
	 * @NoAdminRequired
431
	 * @NoCSRFRequired
432
	 */
433
	public function fileDetails($fileId) {
434
		$details = $this->detailsHelper->getDetails($fileId, $this->userFolder);
435
		if ($details) {
436
			return new JSONResponse($details);
437
		} else {
438
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
439
		}
440
	}
441
442
	/**
443
	 * @NoAdminRequired
444
	 * @NoCSRFRequired
445
	 */
446 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...
447
		$albumId = $this->getIdFromSlug($albumIdOrSlug);
448
		$coverData = $this->coverHelper->getCover($albumId, $this->userId, $this->userFolder);
449
450
		if ($coverData !== null) {
451
			return new FileResponse($coverData);
452
		} else {
453
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
454
		}
455
	}
456
457
	/**
458
	 * @NoAdminRequired
459
	 * @NoCSRFRequired
460
	 */
461 View Code Duplication
	public function cachedCover($hash) {
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...
462
		$coverData = $this->coverHelper->getCoverFromCache($hash, $this->userId);
463
464
		if ($coverData !== null) {
465
			$response =  new FileResponse($coverData);
466
			// instruct also the client-side to cache the result, this is safe
467
			// as the resource URI contains the image hash
468
			self::setClientCaching($response);
469
			return $response;
470
		} else {
471
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
472
		}
473
	}
474
475
	private static function setClientCaching(&$httpResponse, $days=365) {
476
		$httpResponse->cacheFor($days * 24 * 60 * 60);
477
		$httpResponse->addHeader('Pragma', 'cache');
478
	}
479
}
480