Passed
Push — master ( ce9a66...c3a2b5 )
by Pauli
02:52
created

ApiController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 12
nc 1
nop 13
dl 0
loc 25
rs 9.8666
c 1
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 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 - 2024
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\Response;
22
use OCP\Files\Folder;
23
use OCP\IRequest;
24
25
use OCA\Music\AppFramework\BusinessLayer\BusinessLayerException;
26
use OCA\Music\AppFramework\Core\Logger;
27
use OCA\Music\BusinessLayer\GenreBusinessLayer;
28
use OCA\Music\BusinessLayer\TrackBusinessLayer;
29
use OCA\Music\Db\Maintenance;
30
use OCA\Music\Http\ErrorResponse;
31
use OCA\Music\Http\FileStreamResponse;
32
use OCA\Music\Utility\CollectionHelper;
33
use OCA\Music\Utility\CoverHelper;
34
use OCA\Music\Utility\DetailsHelper;
35
use OCA\Music\Utility\LastfmService;
36
use OCA\Music\Utility\LibrarySettings;
37
use OCA\Music\Utility\Scanner;
38
use OCA\Music\Utility\Util;
39
40
class ApiController extends Controller {
41
42
	private TrackBusinessLayer $trackBusinessLayer;
43
	private GenreBusinessLayer $genreBusinessLayer;
44
	private Scanner $scanner;
45
	private CollectionHelper $collectionHelper;
46
	private CoverHelper $coverHelper;
47
	private DetailsHelper $detailsHelper;
48
	private LastfmService $lastfmService;
49
	private Maintenance $maintenance;
50
	private LibrarySettings $librarySettings;
51
	private string $userId;
52
	private Logger $logger;
53
54
	public function __construct(string $appname,
55
								IRequest $request,
56
								TrackBusinessLayer $trackBusinessLayer,
57
								GenreBusinessLayer $genreBusinessLayer,
58
								Scanner $scanner,
59
								CollectionHelper $collectionHelper,
60
								CoverHelper $coverHelper,
61
								DetailsHelper $detailsHelper,
62
								LastfmService $lastfmService,
63
								Maintenance $maintenance,
64
								LibrarySettings $librarySettings,
65
								?string $userId,
66
								Logger $logger) {
67
		parent::__construct($appname, $request);
68
		$this->trackBusinessLayer = $trackBusinessLayer;
69
		$this->genreBusinessLayer = $genreBusinessLayer;
70
		$this->scanner = $scanner;
71
		$this->collectionHelper = $collectionHelper;
72
		$this->coverHelper = $coverHelper;
73
		$this->detailsHelper = $detailsHelper;
74
		$this->lastfmService = $lastfmService;
75
		$this->maintenance = $maintenance;
76
		$this->librarySettings = $librarySettings;
77
		$this->userId = $userId ?? ''; // null case should happen only when the user has already logged out
78
		$this->logger = $logger;
79
	}
80
81
	/**
82
	 * @NoAdminRequired
83
	 * @NoCSRFRequired
84
	 */
85
	public function prepareCollection() {
86
		$hash = $this->collectionHelper->getCachedJsonHash();
87
		if ($hash === null) {
88
			// build the collection but ignore the data for now
89
			$this->collectionHelper->getJson();
90
			$hash = $this->collectionHelper->getCachedJsonHash();
91
		}
92
		$coverToken = $this->coverHelper->createAccessToken($this->userId);
93
94
		return new JSONResponse([
95
			'hash' => $hash,
96
			'cover_token' => $coverToken,
97
			'ignored_articles' => $this->librarySettings->getIgnoredArticles($this->userId)
98
		]);
99
	}
100
101
	/**
102
	 * @NoAdminRequired
103
	 * @NoCSRFRequired
104
	 */
105
	public function collection() {
106
		$collectionJson = $this->collectionHelper->getJson();
107
		$response = new DataDisplayResponse($collectionJson);
108
		$response->addHeader('Content-Type', 'application/json; charset=utf-8');
109
110
		// Instruct the client to cache the result in case it requested the collection with
111
		// the correct hash. The hash could be incorrect if the collection would have changed
112
		// between calls to prepareCollection() and collection().
113
		$requestHash = $this->request->getParam('hash');
114
		$actualHash = $this->collectionHelper->getCachedJsonHash();
115
		if (!empty($actualHash) && $requestHash === $actualHash) {
116
			self::setClientCaching($response, 90); // cache for 3 months
117
		}
118
119
		return $response;
120
	}
121
122
	/**
123
	 * @NoAdminRequired
124
	 * @NoCSRFRequired
125
	 */
126
	public function folders() {
127
		$musicFolder = $this->librarySettings->getFolder($this->userId);
128
		$folders = $this->trackBusinessLayer->findAllFolders($this->userId, $musicFolder);
129
		return new JSONResponse($folders);
130
	}
131
132
	/**
133
	 * @NoAdminRequired
134
	 * @NoCSRFRequired
135
	 */
136
	public function genres() {
137
		$genres = $this->genreBusinessLayer->findAllWithTrackIds($this->userId);
138
		$unscanned =  $this->trackBusinessLayer->findFilesWithoutScannedGenre($this->userId);
139
		return new JSONResponse([
140
			'genres' => \array_map(fn($g) => $g->toApi(), $genres),
141
			'unscanned' => $unscanned
142
		]);
143
	}
144
145
	/**
146
	 * @NoAdminRequired
147
	 * @NoCSRFRequired
148
	 */
149
	public function trackByFileId(int $fileId) {
150
		$track = $this->trackBusinessLayer->findByFileId($fileId, $this->userId);
151
		if ($track !== null) {
152
			return new JSONResponse($track->toCollection());
153
		} else {
154
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
155
		}
156
	}
157
158
	/**
159
	 * @NoAdminRequired
160
	 * @NoCSRFRequired
161
	 */
162
	public function getScanState() {
163
		return new JSONResponse([
164
			'unscannedFiles' => $this->scanner->getUnscannedMusicFileIds($this->userId),
165
			'dirtyFiles' => $this->scanner->getDirtyMusicFileIds($this->userId),
166
			'scannedCount' => $this->trackBusinessLayer->count($this->userId)
167
		]);
168
	}
169
170
	/**
171
	 * @NoAdminRequired
172
	 * @NoCSRFRequired
173
	 * @UseSession to keep the session reserved while execution in progress
174
	 */
175
	public function scan(string $files, $finalize) {
176
		// extract the parameters
177
		$fileIds = \array_map('intval', \explode(',', $files));
178
		$finalize = \filter_var($finalize, FILTER_VALIDATE_BOOLEAN);
179
180
		$filesScanned = $this->scanner->scanFiles($this->userId, $fileIds);
181
182
		$albumCoversUpdated = false;
183
		if ($finalize) {
184
			$albumCoversUpdated = $this->scanner->findAlbumCovers($this->userId);
185
			$this->scanner->findArtistCovers($this->userId);
186
			$totalCount = $this->trackBusinessLayer->count($this->userId);
187
			$this->logger->log("Scanning finished, user $this->userId has $totalCount scanned tracks in total", 'info');
188
		}
189
190
		return new JSONResponse([
191
			'filesScanned' => $filesScanned,
192
			'albumCoversUpdated' => $albumCoversUpdated
193
		]);
194
	}
195
196
	/**
197
	 * @NoAdminRequired
198
	 * @NoCSRFRequired
199
	 * @UseSession to keep the session reserved while execution in progress
200
	 */
201
	public function resetScanned() {
202
		$this->maintenance->resetLibrary($this->userId);
203
		return new JSONResponse(['success' => true]);
204
	}
205
206
	/**
207
	 * @NoAdminRequired
208
	 * @NoCSRFRequired
209
	 */
210
	public function download(int $fileId) {
211
		$nodes = $this->scanner->resolveUserFolder($this->userId)->getById($fileId);
212
		$node = $nodes[0] ?? null;
213
		if ($node instanceof \OCP\Files\File) {
214
			return new FileStreamResponse($node);
215
		}
216
217
		return new ErrorResponse(Http::STATUS_NOT_FOUND, 'file not found');
218
	}
219
220
	/**
221
	 * @NoAdminRequired
222
	 * @NoCSRFRequired
223
	 */
224
	public function filePath(int $fileId) {
225
		$userFolder = $this->scanner->resolveUserFolder($this->userId);
226
		$nodes = $userFolder->getById($fileId);
227
		if (\count($nodes) == 0) {
228
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
229
		} else {
230
			$node = $nodes[0];
231
			$path = $userFolder->getRelativePath($node->getPath());
232
			return new JSONResponse(['path' => Util::urlEncodePath($path)]);
233
		}
234
	}
235
236
	/**
237
	 * @NoAdminRequired
238
	 * @NoCSRFRequired
239
	 */
240
	public function fileInfo(int $fileId) {
241
		$userFolder = $this->scanner->resolveUserFolder($this->userId);
242
		$info = $this->scanner->getFileInfo($fileId, $this->userId, $userFolder);
243
		if ($info) {
244
			return new JSONResponse($info);
245
		} else {
246
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
247
		}
248
	}
249
250
	/**
251
	 * @NoAdminRequired
252
	 * @NoCSRFRequired
253
	 */
254
	public function fileDetails(int $fileId) {
255
		$userFolder = $this->scanner->resolveUserFolder($this->userId);
256
		$details = $this->detailsHelper->getDetails($fileId, $userFolder);
257
		if ($details) {
258
			// metadata extracted, attempt to include also the data from Last.fm
259
			$track = $this->trackBusinessLayer->findByFileId($fileId, $this->userId);
260
			if ($track) {
261
				$details['lastfm'] = $this->lastfmService->getTrackInfo($track->getId(), $this->userId);
262
			} else {
263
				$this->logger->log("Track with file ID $fileId was not found => can't fetch info from Last.fm", 'warn');
264
			}
265
266
			return new JSONResponse($details);
267
		} else {
268
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
269
		}
270
	}
271
272
	/**
273
	 * @NoAdminRequired
274
	 * @NoCSRFRequired
275
	 */
276
	public function scrobble(int $trackId) {
277
		try {
278
			$this->trackBusinessLayer->recordTrackPlayed($trackId, $this->userId);
279
			return new JSONResponse(['success' => true]);
280
		} catch (BusinessLayerException $e) {
281
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
282
		}
283
	}
284
285
	/**
286
	 * @NoAdminRequired
287
	 * @NoCSRFRequired
288
	 */
289
	public function albumDetails(int $albumId) {
290
		try {
291
			$info = $this->lastfmService->getAlbumInfo($albumId, $this->userId);
292
			return new JSONResponse($info);
293
		} catch (BusinessLayerException $e) {
294
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
295
		}
296
	}
297
298
	/**
299
	 * @NoAdminRequired
300
	 * @NoCSRFRequired
301
	 */
302
	public function artistDetails(int $artistId) {
303
		try {
304
			$info = $this->lastfmService->getArtistInfo($artistId, $this->userId);
305
			return new JSONResponse($info);
306
		} catch (BusinessLayerException $e) {
307
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
308
		}
309
	}
310
311
	/**
312
	 * @NoAdminRequired
313
	 * @NoCSRFRequired
314
	 */
315
	public function similarArtists(int $artistId) {
316
		try {
317
			$similar = $this->lastfmService->getSimilarArtists($artistId, $this->userId, /*includeNotPresent=*/true);
318
			return new JSONResponse(\array_map(fn($artist) => [
319
				'id' => $artist->getId(),
320
				'name' => $artist->getName(),
321
				'url' => $artist->getLastfmUrl()
322
			], $similar));
323
		} catch (BusinessLayerException $e) {
324
			return new ErrorResponse(Http::STATUS_NOT_FOUND);
325
		}
326
	}
327
328
	private static function setClientCaching(Response &$httpResponse, int $days=365) : void {
329
		$httpResponse->cacheFor($days * 24 * 60 * 60);
330
		$httpResponse->addHeader('Pragma', 'cache');
331
	}
332
}
333