Passed
Push — master ( 3c5a6f...e94730 )
by Pauli
09:42 queued 11s
created

ApiController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 35
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 17
c 1
b 0
f 0
dl 0
loc 35
rs 9.7
cc 1
nc 1
nop 18

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 declare(strict_types=1);
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;
18
use \OCP\AppFramework\Http;
19
use \OCP\AppFramework\Http\DataDisplayResponse;
20
use \OCP\AppFramework\Http\JSONResponse;
21
use \OCP\AppFramework\Http\RedirectResponse;
22
use \OCP\AppFramework\Http\Response;
23
use \OCP\Files\Folder;
24
use \OCP\IL10N;
25
use \OCP\IRequest;
26
use \OCP\IURLGenerator;
27
28
use \OCA\Music\AppFramework\BusinessLayer\BusinessLayerException;
29
use \OCA\Music\AppFramework\Core\Logger;
30
use \OCA\Music\BusinessLayer\AlbumBusinessLayer;
31
use \OCA\Music\BusinessLayer\ArtistBusinessLayer;
32
use \OCA\Music\BusinessLayer\GenreBusinessLayer;
33
use \OCA\Music\BusinessLayer\TrackBusinessLayer;
34
use \OCA\Music\Db\Album;
35
use \OCA\Music\Db\Artist;
36
use \OCA\Music\Db\Maintenance;
37
use \OCA\Music\Db\Track;
38
use \OCA\Music\Http\ErrorResponse;
39
use \OCA\Music\Http\FileResponse;
40
use \OCA\Music\Http\FileStreamResponse;
41
use \OCA\Music\Utility\CollectionHelper;
42
use \OCA\Music\Utility\CoverHelper;
43
use \OCA\Music\Utility\DetailsHelper;
44
use \OCA\Music\Utility\LastfmService;
45
use \OCA\Music\Utility\Scanner;
46
use \OCA\Music\Utility\UserMusicFolder;
47
use \OCA\Music\Utility\Util;
48
49
class ApiController extends Controller {
50
51
	/** @var IL10N */
52
	private $l10n;
53
	/** @var TrackBusinessLayer */
54
	private $trackBusinessLayer;
55
	/** @var ArtistBusinessLayer */
56
	private $artistBusinessLayer;
57
	/** @var AlbumBusinessLayer */
58
	private $albumBusinessLayer;
59
	/** @var GenreBusinessLayer */
60
	private $genreBusinessLayer;
61
	/** @var Scanner */
62
	private $scanner;
63
	/** @var CollectionHelper */
64
	private $collectionHelper;
65
	/** @var CoverHelper */
66
	private $coverHelper;
67
	/** @var DetailsHelper */
68
	private $detailsHelper;
69
	/** @var LastfmService */
70
	private $lastfmService;
71
	/** @var Maintenance */
72
	private $maintenance;
73
	/** @var UserMusicFolder */
74
	private $userMusicFolder;
75
	/** @var string */
76
	private $userId;
77
	/** @var IURLGenerator */
78
	private $urlGenerator;
79
	/** @var Folder */
80
	private $userFolder;
81
	/** @var Logger */
82
	private $logger;
83
84
	public function __construct(string $appname,
85
								IRequest $request,
86
								IURLGenerator $urlGenerator,
87
								TrackBusinessLayer $trackbusinesslayer,
88
								ArtistBusinessLayer $artistbusinesslayer,
89
								AlbumBusinessLayer $albumbusinesslayer,
90
								GenreBusinessLayer $genreBusinessLayer,
91
								Scanner $scanner,
92
								CollectionHelper $collectionHelper,
93
								CoverHelper $coverHelper,
94
								DetailsHelper $detailsHelper,
95
								LastfmService $lastfmService,
96
								Maintenance $maintenance,
97
								UserMusicFolder $userMusicFolder,
98
								?string $userId, // null if this gets called after the user has logged out
99
								IL10N $l10n,
100
								?Folder $userFolder, // null if this gets called after the user has logged out
101
								Logger $logger) {
102
		parent::__construct($appname, $request);
103
		$this->l10n = $l10n;
104
		$this->trackBusinessLayer = $trackbusinesslayer;
105
		$this->artistBusinessLayer = $artistbusinesslayer;
106
		$this->albumBusinessLayer = $albumbusinesslayer;
107
		$this->genreBusinessLayer = $genreBusinessLayer;
108
		$this->scanner = $scanner;
109
		$this->collectionHelper = $collectionHelper;
110
		$this->coverHelper = $coverHelper;
111
		$this->detailsHelper = $detailsHelper;
112
		$this->lastfmService = $lastfmService;
113
		$this->maintenance = $maintenance;
114
		$this->userMusicFolder = $userMusicFolder;
115
		$this->userId = $userId;
116
		$this->urlGenerator = $urlGenerator;
117
		$this->userFolder = $userFolder;
118
		$this->logger = $logger;
119
	}
120
121
	/**
122
	 * Extracts the id from an unique slug (id-slug)
123
	 * @param string|int $slug the slug
124
	 * @return int the id
125
	 */
126
	protected static function getIdFromSlug($slug) : int {
127
		if (\is_string($slug)) {
128
			$split = \explode('-', $slug, 2);
129
			return (int)$split[0];
130
		} elseif (\is_int($slug)) {
0 ignored issues
show
introduced by
The condition is_int($slug) is always true.
Loading history...
131
			return $slug;
132
		} else {
133
			throw new \InvalidArgumentException();
134
		}
135
	}
136
137
	/**
138
	 * @NoAdminRequired
139
	 * @NoCSRFRequired
140
	 */
141
	public function prepareCollection() {
142
		$hash = $this->collectionHelper->getCachedJsonHash();
143
		if ($hash === null) {
144
			// build the collection but ignore the data for now
145
			$this->collectionHelper->getJson();
146
			$hash = $this->collectionHelper->getCachedJsonHash();
147
		}
148
		$coverToken = $this->coverHelper->createAccessToken($this->userId);
149
150
		return new JSONResponse([
151
			'hash' => $hash,
152
			'cover_token' => $coverToken
153
		]);
154
	}
155
156
	/**
157
	 * @NoAdminRequired
158
	 * @NoCSRFRequired
159
	 */
160
	public function collection() {
161
		$collectionJson = $this->collectionHelper->getJson();
162
		$response = new DataDisplayResponse($collectionJson);
163
		$response->addHeader('Content-Type', 'application/json; charset=utf-8');
164
165
		// Instruct the client to cache the result in case it requested the collection with
166
		// the correct hash. The hash could be incorrect if the collection would have changed
167
		// between calls to prepareCollection() and colletion().
168
		$requestHash = $this->request->getParam('hash');
169
		$actualHash = $this->collectionHelper->getCachedJsonHash();
170
		if (!empty($actualHash) && $requestHash === $actualHash) {
171
			self::setClientCaching($response, 90); // cache for 3 months
172
		}
173
174
		return $response;
175
	}
176
177
	/**
178
	 * @NoAdminRequired
179
	 * @NoCSRFRequired
180
	 */
181
	public function folders() {
182
		$musicFolder = $this->userMusicFolder->getFolder($this->userId);
183
		$folders = $this->trackBusinessLayer->findAllFolders($this->userId, $musicFolder);
184
		return new JSONResponse($folders);
185
	}
186
187
	/**
188
	 * @NoAdminRequired
189
	 * @NoCSRFRequired
190
	 */
191
	public function genres() {
192
		$genres = $this->genreBusinessLayer->findAllWithTrackIds($this->userId);
193
		$unscanned =  $this->trackBusinessLayer->findFilesWithoutScannedGenre($this->userId);
194
		return new JSONResponse([
195
			'genres' => \array_map(function ($g) {
196
				return $g->toApi();
197
			}, $genres),
198
			'unscanned' => $unscanned
199
		]);
200
	}
201
202
	/**
203
	 * @NoAdminRequired
204
	 * @NoCSRFRequired
205
	 */
206
	public function artists($fulltree, $albums) {
207
		$fulltree = \filter_var($fulltree, FILTER_VALIDATE_BOOLEAN);
208
		$includeAlbums = \filter_var($albums, FILTER_VALIDATE_BOOLEAN);
209
		/** @var Artist[] $artists */
210
		$artists = $this->artistBusinessLayer->findAll($this->userId);
211
212
		$artists = \array_map(function ($a) use ($fulltree, $includeAlbums) {
213
			return $this->artistToApi($a, $includeAlbums || $fulltree, $fulltree);
214
		}, $artists);
215
216
		return new JSONResponse($artists);
217
	}
218
219
	/**
220
	 * @NoAdminRequired
221
	 * @NoCSRFRequired
222
	 */
223
	public function artist($artistIdOrSlug, $fulltree) {
224
		$fulltree = \filter_var($fulltree, FILTER_VALIDATE_BOOLEAN);
225
		$artistId = $this->getIdFromSlug($artistIdOrSlug);
226
		/** @var Artist $artist */
227
		$artist = $this->artistBusinessLayer->find($artistId, $this->userId);
228
		$artist = $this->artistToApi($artist, $fulltree, $fulltree);
229
		return new JSONResponse($artist);
230
	}
231
232
	/**
233
	 * Return given artist in Shia API format
234
	 * @param Artist $artist
235
	 * @param boolean $includeAlbums
236
	 * @param boolean $includeTracks (ignored if $includeAlbums==false)
237
	 * @return array
238
	 */
239
	private function artistToApi(Artist $artist, bool $includeAlbums, bool $includeTracks) : array {
240
		$artistInApi = $artist->toAPI($this->urlGenerator, $this->l10n);
241
		if ($includeAlbums) {
242
			$artistId = $artist->getId();
243
			$albums = $this->albumBusinessLayer->findAllByArtist($artistId, $this->userId);
244
245
			$artistInApi['albums'] = \array_map(function ($a) use ($includeTracks) {
246
				return $this->albumToApi($a, $includeTracks, false);
247
			}, $albums);
248
		}
249
		return $artistInApi;
250
	}
251
252
	/**
253
	 * @NoAdminRequired
254
	 * @NoCSRFRequired
255
	 */
256
	public function albums(?int $artist, $fulltree) {
257
		$fulltree = \filter_var($fulltree, FILTER_VALIDATE_BOOLEAN);
258
		if ($artist !== null) {
259
			$albums = $this->albumBusinessLayer->findAllByArtist($artist, $this->userId);
260
		} else {
261
			$albums = $this->albumBusinessLayer->findAll($this->userId);
262
		}
263
264
		$albums = \array_map(function ($a) use ($fulltree) {
265
			return $this->albumToApi($a, $fulltree, $fulltree);
266
		}, $albums);
267
268
		return new JSONResponse($albums);
269
	}
270
271
	/**
272
	 * @NoAdminRequired
273
	 * @NoCSRFRequired
274
	 */
275
	public function album($albumIdOrSlug, $fulltree) {
276
		$fulltree = \filter_var($fulltree, FILTER_VALIDATE_BOOLEAN);
277
		$albumId = $this->getIdFromSlug($albumIdOrSlug);
278
		$album = $this->albumBusinessLayer->find($albumId, $this->userId);
279
		$album = $this->albumToApi($album, $fulltree, $fulltree);
280
		return new JSONResponse($album);
281
	}
282
283
	/**
284
	 * Return given album in the Shiva API format
285
	 */
286
	private function albumToApi(Album $album, bool $includeTracks, bool $includeArtists) : array {
287
		$albumInApi = $album->toAPI($this->urlGenerator, $this->l10n);
288
289
		if ($includeTracks) {
290
			$albumId = $album->getId();
291
			$tracks = $this->trackBusinessLayer->findAllByAlbum($albumId, $this->userId);
292
			$albumInApi['tracks'] = \array_map(function ($t) {
293
				return $t->toAPI($this->urlGenerator);
294
			}, $tracks);
295
		}
296
297
		if ($includeArtists) {
298
			$artistIds = $album->getArtistIds();
299
			$artists = $this->artistBusinessLayer->findById($artistIds, $this->userId);
300
			$albumInApi['artists'] = \array_map(function ($a) {
301
				return $a->toAPI($this->urlGenerator, $this->l10n);
302
			}, $artists);
303
		}
304
305
		return $albumInApi;
306
	}
307
308
	/**
309
	 * @NoAdminRequired
310
	 * @NoCSRFRequired
311
	 */
312
	public function tracks($artist, $album, $fulltree) {
313
		$fulltree = \filter_var($fulltree, FILTER_VALIDATE_BOOLEAN);
314
		if ($artist) {
315
			$tracks = $this->trackBusinessLayer->findAllByArtist($artist, $this->userId);
316
		} elseif ($album) {
317
			$tracks = $this->trackBusinessLayer->findAllByAlbum($album, $this->userId);
318
		} else {
319
			$tracks = $this->trackBusinessLayer->findAll($this->userId);
320
		}
321
		foreach ($tracks as &$track) {
322
			$artistId = $track->getArtistId();
323
			$albumId = $track->getAlbumId();
324
			$track = $track->toAPI($this->urlGenerator);
325
			if ($fulltree) {
326
				/** @var Artist $artist */
327
				$artist = $this->artistBusinessLayer->find($artistId, $this->userId);
328
				$track['artist'] = $artist->toAPI($this->urlGenerator, $this->l10n);
329
				$album = $this->albumBusinessLayer->find($albumId, $this->userId);
330
				$track['album'] = $album->toAPI($this->urlGenerator, $this->l10n);
331
			}
332
		}
333
		return new JSONResponse($tracks);
334
	}
335
336
	/**
337
	 * @NoAdminRequired
338
	 * @NoCSRFRequired
339
	 */
340
	public function track($trackIdOrSlug) {
341
		$trackId = $this->getIdFromSlug($trackIdOrSlug);
342
		/** @var Track $track */
343
		$track = $this->trackBusinessLayer->find($trackId, $this->userId);
344
		return new JSONResponse($track->toAPI($this->urlGenerator));
345
	}
346
347
	/**
348
	 * @NoAdminRequired
349
	 * @NoCSRFRequired
350
	 */
351
	public function trackByFileId(int $fileId) {
352
		$track = $this->trackBusinessLayer->findByFileId($fileId, $this->userId);
353
		if ($track !== null) {
354
			return new JSONResponse($track->toCollection($this->l10n));
355
		} else {
356
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
357
		}
358
	}
359
360
	/**
361
	 * @NoAdminRequired
362
	 * @NoCSRFRequired
363
	 */
364
	public function getScanState() {
365
		return new JSONResponse([
366
			'unscannedFiles' => $this->scanner->getUnscannedMusicFileIds($this->userId),
367
			'scannedCount' => $this->trackBusinessLayer->count($this->userId)
368
		]);
369
	}
370
371
	/**
372
	 * @NoAdminRequired
373
	 * @UseSession to keep the session reserved while execution in progress
374
	 */
375
	public function scan(string $files, $finalize) {
376
		// extract the parameters
377
		$fileIds = \array_map('intval', \explode(',', $files));
378
		$finalize = \filter_var($finalize, FILTER_VALIDATE_BOOLEAN);
379
380
		$filesScanned = $this->scanner->scanFiles($this->userId, $this->userFolder, $fileIds);
381
382
		$coversUpdated = false;
383
		if ($finalize) {
384
			$coversUpdated = $this->scanner->findAlbumCovers($this->userId)
385
							|| $this->scanner->findArtistCovers($this->userId);
386
			$totalCount = $this->trackBusinessLayer->count($this->userId);
387
			$this->logger->log("Scanning finished, user $this->userId has $totalCount scanned tracks in total", 'info');
388
		}
389
390
		return new JSONResponse([
391
			'filesScanned' => $filesScanned,
392
			'coversUpdated' => $coversUpdated
393
		]);
394
	}
395
396
	/**
397
	 * @NoAdminRequired
398
	 * @UseSession to keep the session reserved while execution in progress
399
	 */
400
	public function resetScanned() {
401
		$this->maintenance->resetDb($this->userId);
402
		return new JSONResponse(['success' => true]);
403
	}
404
405
	/**
406
	 * @NoAdminRequired
407
	 * @NoCSRFRequired
408
	 */
409
	public function download(int $fileId) {
410
		$nodes = $this->userFolder->getById($fileId);
411
		$node = $nodes[0] ?? null;
412
		if ($node instanceof \OCP\Files\File) {
413
			return new FileStreamResponse($node);
414
		}
415
416
		return new ErrorResponse(Http::STATUS_NOT_FOUND, 'file not found');
417
	}
418
419
	/**
420
	 * @NoAdminRequired
421
	 * @NoCSRFRequired
422
	 */
423
	public function filePath(int $fileId) {
424
		$nodes = $this->userFolder->getById($fileId);
425
		if (\count($nodes) == 0) {
426
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
427
		} else {
428
			$node = $nodes[0];
429
			$path = $this->userFolder->getRelativePath($node->getPath());
430
			return new JSONResponse(['path' => Util::urlEncodePath($path)]);
431
		}
432
	}
433
434
	/**
435
	 * @NoAdminRequired
436
	 * @NoCSRFRequired
437
	 */
438
	public function fileInfo(int $fileId) {
439
		$info = $this->scanner->getFileInfo($fileId, $this->userId, $this->userFolder);
440
		if ($info) {
441
			return new JSONResponse($info);
442
		} else {
443
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
444
		}
445
	}
446
447
	/**
448
	 * @NoAdminRequired
449
	 * @NoCSRFRequired
450
	 */
451
	public function fileDetails(int $fileId) {
452
		$details = $this->detailsHelper->getDetails($fileId, $this->userFolder);
453
		if ($details) {
454
			// metadata extracted, attempt to include also the data from Last.fm
455
			$track = $this->trackBusinessLayer->findByFileId($fileId, $this->userId);
456
			if ($track) {
457
				$details['lastfm'] = $this->lastfmService->getTrackInfo($track->getId(), $this->userId);
458
			} else {
459
				$this->logger->log("Track with file ID $fileId was not found => can't fetch info from Last.fm", 'warn');
460
			}
461
462
			return new JSONResponse($details);
463
		} else {
464
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
465
		}
466
	}
467
468
	/**
469
	 * @NoAdminRequired
470
	 * @NoCSRFRequired
471
	 */
472
	public function scrobble(int $trackId) {
473
		try {
474
			$this->trackBusinessLayer->recordTrackPlayed($trackId, $this->userId);
475
			return new JSONResponse(['success' => true]);
476
		} catch (BusinessLayerException $e) {
477
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
478
		}
479
	}
480
481
	/**
482
	 * @NoAdminRequired
483
	 * @NoCSRFRequired
484
	 */
485
	public function albumDetails($albumIdOrSlug) {
486
		try {
487
			$albumId = $this->getIdFromSlug($albumIdOrSlug);
488
			$info = $this->lastfmService->getAlbumInfo($albumId, $this->userId);
489
			return new JSONResponse($info);
490
		} catch (BusinessLayerException $e) {
491
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
492
		}
493
	}
494
495
	/**
496
	 * @NoAdminRequired
497
	 * @NoCSRFRequired
498
	 */
499
	public function artistDetails($artistIdOrSlug) {
500
		try {
501
			$artistId = $this->getIdFromSlug($artistIdOrSlug);
502
			$info = $this->lastfmService->getArtistInfo($artistId, $this->userId);
503
			return new JSONResponse($info);
504
		} catch (BusinessLayerException $e) {
505
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
506
		}
507
	}
508
509
	/**
510
	 * @NoAdminRequired
511
	 * @NoCSRFRequired
512
	 */
513
	public function similarArtists($artistIdOrSlug) {
514
		try {
515
			$artistId = $this->getIdFromSlug($artistIdOrSlug);
516
			$similar = $this->lastfmService->getSimilarArtists($artistId, $this->userId, /*includeNotPresent=*/true);
517
			return new JSONResponse(\array_map(function ($artist) {
518
				return [
519
					'id' => $artist->getId(),
520
					'name' => $artist->getName(),
521
					'url' => $artist->getLastfmUrl()
522
				];
523
			}, $similar));
524
		} catch (BusinessLayerException $e) {
525
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
526
		}
527
	}
528
529
	/**
530
	 * @PublicPage
531
	 * @NoCSRFRequired
532
	 */
533
	public function albumCover($albumIdOrSlug, $originalSize, $coverToken) {
534
		try {
535
			$userId = $this->userId ?? $this->coverHelper->getUserForAccessToken($coverToken);
536
			$albumId = $this->getIdFromSlug($albumIdOrSlug);
537
			$album = $this->albumBusinessLayer->find($albumId, $userId);
538
			return $this->cover($album, $userId, $originalSize);
539
		} catch (BusinessLayerException | \OutOfBoundsException $ex) {
540
			$this->logger->log("Failed to get the requested cover: $ex", 'debug');
541
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
542
		}
543
	}
544
545
	/**
546
	 * @PublicPage
547
	 * @NoCSRFRequired
548
	 */
549
	public function artistCover($artistIdOrSlug, $originalSize, $coverToken) {
550
		try {
551
			$userId = $this->userId ?? $this->coverHelper->getUserForAccessToken($coverToken);
552
			$artistId = $this->getIdFromSlug($artistIdOrSlug);
553
			$artist = $this->artistBusinessLayer->find($artistId, $userId);
554
			return $this->cover($artist, $userId, $originalSize);
555
		} catch (BusinessLayerException | \OutOfBoundsException $ex) {
556
			$this->logger->log("Failed to get the requested cover: $ex", 'debug');
557
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
558
		}
559
	}
560
561
	private function cover($entity, $userId, $originalSize) {
562
		$originalSize = \filter_var($originalSize, FILTER_VALIDATE_BOOLEAN);
563
		$userFolder = $this->userFolder ?? $this->scanner->resolveUserFolder($userId);
564
565
		if ($originalSize) {
566
			// cover requested in original size, without scaling or cropping
567
			$cover = $this->coverHelper->getCover($entity, $userId, $userFolder, CoverHelper::DO_NOT_CROP_OR_SCALE);
568
			if ($cover !== null) {
569
				return new FileResponse($cover);
570
			} else {
571
				return new ErrorResponse(Http::STATUS_NOT_FOUND);
572
			}
573
		} else {
574
			$coverAndHash = $this->coverHelper->getCoverAndHash($entity, $userId, $userFolder);
575
576
			if ($coverAndHash['hash'] !== null && $this->userId !== null) {
577
				// Cover is in cache. Return a redirection response so that the client
578
				// will fetch the content through a cacheable route.
579
				// The redirection is not used in case this is a call from the Firefox mediaSession API with not
580
				// logged in user.
581
				$link = $this->urlGenerator->linkToRoute('music.api.cachedCover', ['hash' => $coverAndHash['hash']]);
582
				return new RedirectResponse($link);
583
			} elseif ($coverAndHash['data'] !== null) {
584
				return new FileResponse($coverAndHash['data']);
585
			} else {
586
				return new ErrorResponse(Http::STATUS_NOT_FOUND);
587
			}
588
		}
589
	}
590
591
	/**
592
	 * @PublicPage
593
	 * @NoCSRFRequired
594
	 */
595
	public function cachedCover(string $hash, ?string $coverToken) {
596
		try {
597
			$userId = $this->userId ?? $this->coverHelper->getUserForAccessToken($coverToken);
598
			$coverData = $this->coverHelper->getCoverFromCache($hash, $userId);
599
			if ($coverData === null) {
600
				throw new \OutOfBoundsException("Cover with hash $hash not found");
601
			}
602
			$response =  new FileResponse($coverData);
603
			// instruct also the client-side to cache the result, this is safe
604
			// as the resource URI contains the image hash
605
			self::setClientCaching($response);
606
			return $response;
607
		} catch (\OutOfBoundsException $ex) {
608
			$this->logger->log("Failed to get the requested cover: $ex", 'debug');
609
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
610
		}
611
	}
612
613
	private static function setClientCaching(Response &$httpResponse, int $days=365) : void {
614
		$httpResponse->cacheFor($days * 24 * 60 * 60);
615
		$httpResponse->addHeader('Pragma', 'cache');
616
	}
617
}
618