Passed
Pull Request — master (#1078)
by Pauli
05:57 queued 03:17
created

AlbumMapper   F

Complexity

Total Complexity 72

Size/Duplication

Total Lines 499
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 200
c 3
b 0
f 0
dl 0
loc 499
rs 2.64
wmc 72

24 Methods

Rating   Name   Duplication   Size   Complexity  
A selectEntities() 0 6 1
A __construct() 0 2 1
A countByAlbumArtist() 0 8 1
A findAlbumsWithCoversForTracks() 0 10 1
A findAlbumCover() 0 28 4
A removeCovers() 0 22 3
A findAllByAlbumArtist() 0 4 1
A updateFolderCover() 0 19 2
A getAlbumsWithoutCover() 0 21 3
A countByArtist() 0 14 1
A setCover() 0 6 1
A findAllByGenre() 0 8 1
A findAllByArtist() 0 14 1
A formatSortingClause() 0 7 3
A getAlbumTracksPlayCount() 0 13 2
A findAllByNameRecursive() 0 7 1
A getLatestAlbumPlayTimes() 0 13 2
A getYearsByAlbumId() 0 18 3
A getDiscCountByAlbumId() 0 18 3
A getPerformingArtistsByAlbumId() 0 17 3
A getFurthestAlbumPlayTimes() 0 13 2
A getGenresByAlbumId() 0 24 3
A findUniqueEntity() 0 3 1
D advFormatSqlCondition() 0 31 28

How to fix   Complexity   

Complex Class

Complex classes like AlbumMapper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AlbumMapper, and based on these observations, apply Extract Interface, too.

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 2016 - 2023
13
 */
14
15
namespace OCA\Music\Db;
16
17
use OCA\Music\Utility\Util;
18
19
use OCP\IDBConnection;
20
21
/**
22
 * Type hint a base class methdo to help Scrutinizer
23
 * @method Album updateOrInsert(Album $album)
24
 * @phpstan-extends BaseMapper<Album>
25
 */
26
class AlbumMapper extends BaseMapper {
27
	public function __construct(IDBConnection $db) {
28
		parent::__construct($db, 'music_albums', Album::class, 'name');
29
	}
30
31
	/**
32
	 * Override the base implementation to include data from multiple tables
33
	 *
34
	 * {@inheritdoc}
35
	 * @see BaseMapper::selectEntities()
36
	 */
37
	protected function selectEntities(string $condition, string $extension=null) : string {
38
		return "SELECT `*PREFIX*music_albums`.*, `artist`.`name` AS `album_artist_name`
39
				FROM `*PREFIX*music_albums`
40
				INNER JOIN `*PREFIX*music_artists` `artist`
41
				ON `*PREFIX*music_albums`.`album_artist_id` = `artist`.`id`
42
				WHERE $condition $extension";
43
	}
44
45
	/**
46
	 * Overridden from \OCA\Music\Db\BaseMapper to add support for sorting by artist.
47
	 *
48
	 * {@inheritdoc}
49
	 * @see BaseMapper::formatSortingClause()
50
	 */
51
	protected function formatSortingClause(int $sortBy, bool $invertSort = false) : ?string {
52
		if ($sortBy === SortBy::Parent) {
53
			// Note: the alternative form "LOWER(`album_artist_name`) wouldn't work on PostgreSQL, see https://github.com/owncloud/music/issues/1046
54
			$dir = $invertSort ? 'DESC' : 'ASC';
55
			return "ORDER BY LOWER(`artist`.`name`) $dir, LOWER(`*PREFIX*music_albums`.`name`) $dir";
56
		} else {
57
			return parent::formatSortingClause($sortBy, $invertSort);
58
		}
59
	}
60
61
	/**
62
	 * returns artist IDs mapped to album IDs
63
	 * does not include album_artist_id
64
	 *
65
	 * @param integer[]|null $albumIds IDs of the albums; get all albums of the user if null given
66
	 * @param string $userId the user ID
67
	 * @return array int => int[], keys are albums IDs and values are arrays of artist IDs
68
	 */
69
	public function getPerformingArtistsByAlbumId(?array $albumIds, string $userId) : array {
70
		$sql = 'SELECT DISTINCT `track`.`album_id`, `track`.`artist_id`
71
				FROM `*PREFIX*music_tracks` `track`
72
				WHERE `track`.`user_id` = ? ';
73
		$params = [$userId];
74
75
		if ($albumIds !== null) {
0 ignored issues
show
introduced by
The condition $albumIds !== null is always true.
Loading history...
76
			$sql .= 'AND `track`.`album_id` IN ' . $this->questionMarks(\count($albumIds));
77
			$params = \array_merge($params, $albumIds);
78
		}
79
80
		$result = $this->execute($sql, $params);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

80
		$result = /** @scrutinizer ignore-deprecated */ $this->execute($sql, $params);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
81
		$artistIds = [];
82
		while ($row = $result->fetch()) {
83
			$artistIds[$row['album_id']][] = (int)$row['artist_id'];
84
		}
85
		return $artistIds;
86
	}
87
88
	/**
89
	 * returns release years mapped to album IDs
90
	 *
91
	 * @param integer[]|null $albumIds IDs of the albums; get all albums of the user if null given
92
	 * @param string $userId the user ID
93
	 * @return array int => int[], keys are albums IDs and values are arrays of years
94
	 */
95
	public function getYearsByAlbumId(?array $albumIds, string $userId) : array {
96
		$sql = 'SELECT DISTINCT `track`.`album_id`, `track`.`year`
97
				FROM `*PREFIX*music_tracks` `track`
98
				WHERE `track`.`user_id` = ?
99
				AND `track`.`year` IS NOT NULL ';
100
		$params = [$userId];
101
102
		if ($albumIds !== null) {
0 ignored issues
show
introduced by
The condition $albumIds !== null is always true.
Loading history...
103
			$sql .= 'AND `track`.`album_id` IN ' . $this->questionMarks(\count($albumIds));
104
			$params = \array_merge($params, $albumIds);
105
		}
106
107
		$result = $this->execute($sql, $params);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

107
		$result = /** @scrutinizer ignore-deprecated */ $this->execute($sql, $params);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
108
		$years = [];
109
		while ($row = $result->fetch()) {
110
			$years[$row['album_id']][] = (int)$row['year'];
111
		}
112
		return $years;
113
	}
114
115
	/**
116
	 * returns genres mapped to album IDs
117
	 *
118
	 * @param integer[]|null $albumIds IDs of the albums; get all albums of the user if null given
119
	 * @param string $userId the user ID
120
	 * @return array int => Genre[], keys are albums IDs and values are arrays of *partial* Genre objects (only id and name properties set)
121
	 */
122
	public function getGenresByAlbumId(?array $albumIds, string $userId) : array {
123
		$sql = 'SELECT DISTINCT `album_id`, `genre_id`, `*PREFIX*music_genres`.`name` AS `genre_name`
124
				FROM `*PREFIX*music_tracks`
125
				LEFT JOIN `*PREFIX*music_genres`
126
				ON `genre_id` = `*PREFIX*music_genres`.`id`
127
				WHERE `*PREFIX*music_tracks`.`user_id` = ?
128
				AND `genre_id` IS NOT NULL ';
129
		$params = [$userId];
130
131
		if ($albumIds !== null) {
0 ignored issues
show
introduced by
The condition $albumIds !== null is always true.
Loading history...
132
			$sql .= 'AND `album_id` IN ' . $this->questionMarks(\count($albumIds));
133
			$params = \array_merge($params, $albumIds);
134
		}
135
136
		$result = $this->execute($sql, $params);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

136
		$result = /** @scrutinizer ignore-deprecated */ $this->execute($sql, $params);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
137
		$genres = [];
138
		while ($row = $result->fetch()) {
139
			$genre = new Genre();
140
			$genre->setUserId($userId);
141
			$genre->setId((int)$row['genre_id']);
142
			$genre->setName($row['genre_name']);
143
			$genres[$row['album_id']][] = $genre;
144
		}
145
		return $genres;
146
	}
147
148
	/**
149
	 * returns number of disks per album ID
150
	 *
151
	 * @param integer[]|null $albumIds IDs of the albums; get all albums of the user if null given
152
	 * @param string $userId the user ID
153
	 * @return array int => int, keys are albums IDs and values are disk counts
154
	 */
155
	public function getDiscCountByAlbumId(?array $albumIds, string $userId) : array {
156
		$sql = 'SELECT `album_id`, MAX(`disk`) AS `disc_count`
157
				FROM `*PREFIX*music_tracks`
158
				WHERE `user_id` = ?
159
				GROUP BY `album_id` ';
160
		$params = [$userId];
161
162
		if ($albumIds !== null) {
0 ignored issues
show
introduced by
The condition $albumIds !== null is always true.
Loading history...
163
			$sql .= 'HAVING `album_id` IN ' . $this->questionMarks(\count($albumIds));
164
			$params = \array_merge($params, $albumIds);
165
		}
166
167
		$result = $this->execute($sql, $params);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

167
		$result = /** @scrutinizer ignore-deprecated */ $this->execute($sql, $params);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
168
		$diskCountByAlbum = [];
169
		while ($row = $result->fetch()) {
170
			$diskCountByAlbum[$row['album_id']] = (int)$row['disc_count'];
171
		}
172
		return $diskCountByAlbum;
173
	}
174
175
	/**
176
	 * returns summed track play counts of each album of the user, omittig albums which have never been played
177
	 *
178
	 * @return array [int => int], keys are album IDs and values are play count sums; ordered largest counts first
179
	 */
180
	public function getAlbumTracksPlayCount(string $userId, ?int $limit=null, ?int $offset=null) : array {
181
		$sql = 'SELECT `album_id`, SUM(`play_count`) AS `sum_count`
182
				FROM `*PREFIX*music_tracks`
183
				WHERE `user_id` = ? AND `play_count` > 0
184
				GROUP BY `album_id`
185
				ORDER BY `sum_count` DESC, `album_id`'; // the second criterion is just to make the order predictable on even counts
186
187
		$result = $this->execute($sql, [$userId], $limit, $offset);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

187
		$result = /** @scrutinizer ignore-deprecated */ $this->execute($sql, [$userId], $limit, $offset);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
188
		$playCountByAlbum = [];
189
		while ($row = $result->fetch()) {
190
			$playCountByAlbum[$row['album_id']] = (int)$row['sum_count'];
191
		}
192
		return $playCountByAlbum;
193
	}
194
195
	/**
196
	 * returns the latest play time of each album of the user, omittig albums which have never been played
197
	 *
198
	 * @return array [int => string], keys are album IDs and values are date-times; ordered latest times first
199
	 */
200
	public function getLatestAlbumPlayTimes(string $userId, ?int $limit=null, ?int $offset=null) : array {
201
		$sql = 'SELECT `album_id`, MAX(`last_played`) AS `latest_time`
202
				FROM `*PREFIX*music_tracks`
203
				WHERE `user_id` = ? AND `last_played` IS NOT NULL
204
				GROUP BY `album_id`
205
				ORDER BY `latest_time` DESC';
206
207
		$result = $this->execute($sql, [$userId], $limit, $offset);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

207
		$result = /** @scrutinizer ignore-deprecated */ $this->execute($sql, [$userId], $limit, $offset);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
208
		$latestTimeByAlbum = [];
209
		while ($row = $result->fetch()) {
210
			$latestTimeByAlbum[$row['album_id']] = $row['latest_time'];
211
		}
212
		return $latestTimeByAlbum;
213
	}
214
215
	/**
216
	 * returns the latest play time of each album of the user, including albums which have never been played
217
	 *
218
	 * @return array [int => ?string], keys are album IDs and values are date-times (or null for never played);
219
	 *									ordered furthest times first
220
	 */
221
	public function getFurthestAlbumPlayTimes(string $userId, ?int $limit=null, ?int $offset=null) : array {
222
		$sql = 'SELECT `album_id`, MAX(`last_played`) AS `latest_time`
223
				FROM `*PREFIX*music_tracks`
224
				WHERE `user_id` = ?
225
				GROUP BY `album_id`
226
				ORDER BY `latest_time` ASC';
227
228
		$result = $this->execute($sql, [$userId], $limit, $offset);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

228
		$result = /** @scrutinizer ignore-deprecated */ $this->execute($sql, [$userId], $limit, $offset);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
229
		$latestTimeByAlbum = [];
230
		while ($row = $result->fetch()) {
231
			$latestTimeByAlbum[$row['album_id']] = $row['latest_time'];
232
		}
233
		return $latestTimeByAlbum;
234
	}
235
236
	/**
237
	 * @return Album[]
238
	 */
239
	public function findAllByNameRecursive(string $name, string $userId, ?int $limit=null, ?int $offset=null) : array {
240
		$condition = '( LOWER(`artist`.`name`) LIKE LOWER(?) OR
241
						LOWER(`*PREFIX*music_albums`.`name`) LIKE LOWER(?) )';
242
		$sql = $this->selectUserEntities($condition, 'ORDER BY LOWER(`*PREFIX*music_albums`.`name`)');
243
		$name = BaseMapper::prepareSubstringSearchPattern($name);
244
		$params = [$userId, $name, $name];
245
		return $this->findEntities($sql, $params, $limit, $offset);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...dMapper::findEntities() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

245
		return /** @scrutinizer ignore-deprecated */ $this->findEntities($sql, $params, $limit, $offset);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
246
	}
247
248
	/**
249
	 * returns albums of a specified artist
250
	 * The artist may be an album_artist or the artist of a track
251
	 *
252
	 * @param integer $artistId
253
	 * @return Album[]
254
	 */
255
	public function findAllByArtist(int $artistId, string $userId, ?int $limit=null, ?int $offset=null) : array {
256
		$sql = $this->selectEntities(
257
				'`*PREFIX*music_albums`.`id` IN (
258
					SELECT DISTINCT `album`.`id`
259
					FROM `*PREFIX*music_albums` `album`
260
					WHERE `album`.`album_artist_id` = ?
261
						UNION
262
					SELECT DISTINCT `track`.`album_id`
263
					FROM `*PREFIX*music_tracks` `track`
264
					WHERE `track`.`artist_id` = ?
265
				) AND `*PREFIX*music_albums`.`user_id` = ?',
266
				'ORDER BY LOWER(`*PREFIX*music_albums`.`name`)');
267
		$params = [$artistId, $artistId, $userId];
268
		return $this->findEntities($sql, $params, $limit, $offset);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...dMapper::findEntities() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

268
		return /** @scrutinizer ignore-deprecated */ $this->findEntities($sql, $params, $limit, $offset);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
269
	}
270
271
	/**
272
	 * returns albums of a specified artists
273
	 * The artist must be album_artist on the album, artists of individual tracks are not considered
274
	 *
275
	 * @param int[] $artistIds
276
	 * @return Album[]
277
	 */
278
	public function findAllByAlbumArtist(array $artistIds, string $userId, ?int $limit=null, ?int $offset=null) : array {
279
		$sql = $this->selectUserEntities('`album_artist_id` IN ' . $this->questionMarks(\count($artistIds)));
280
		$params = \array_merge([$userId], $artistIds);
281
		return $this->findEntities($sql, $params, $limit, $offset);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...dMapper::findEntities() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

281
		return /** @scrutinizer ignore-deprecated */ $this->findEntities($sql, $params, $limit, $offset);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
282
	}
283
284
	/**
285
	 * @return Album[]
286
	 */
287
	public function findAllByGenre(int $genreId, string $userId, int $limit=null, int $offset=null) : array {
288
		$sql = $this->selectUserEntities('EXISTS '.
289
				'(SELECT 1 FROM `*PREFIX*music_tracks` `track`
290
				  WHERE `*PREFIX*music_albums`.`id` = `track`.`album_id`
291
				  AND `track`.`genre_id` = ?)');
292
293
		$params = [$userId, $genreId];
294
		return $this->findEntities($sql, $params, $limit, $offset);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...dMapper::findEntities() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

294
		return /** @scrutinizer ignore-deprecated */ $this->findEntities($sql, $params, $limit, $offset);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
295
	}
296
297
	/**
298
	 * @return boolean True if one or more albums were influenced
299
	 */
300
	public function updateFolderCover(int $coverFileId, int $folderId) : bool {
301
		$sql = 'SELECT DISTINCT `tracks`.`album_id`
302
				FROM `*PREFIX*music_tracks` `tracks`
303
				JOIN `*PREFIX*filecache` `files` ON `tracks`.`file_id` = `files`.`fileid`
304
				WHERE `files`.`parent` = ?';
305
		$params = [$folderId];
306
		$result = $this->execute($sql, $params);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

306
		$result = /** @scrutinizer ignore-deprecated */ $this->execute($sql, $params);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
307
308
		$updated = false;
309
		if ($result->rowCount()) {
310
			$sql = 'UPDATE `*PREFIX*music_albums`
311
					SET `cover_file_id` = ?
312
					WHERE `cover_file_id` IS NULL AND `id` IN (?)';
313
			$params = [$coverFileId, \join(",", $result->fetchAll(\PDO::FETCH_COLUMN))];
314
			$result = $this->execute($sql, $params);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

314
			$result = /** @scrutinizer ignore-deprecated */ $this->execute($sql, $params);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
315
			$updated = $result->rowCount() > 0;
316
		}
317
318
		return $updated;
319
	}
320
321
	/**
322
	 * Set file ID to be used as cover for an album
323
	 */
324
	public function setCover(?int $coverFileId, int $albumId) : void {
325
		$sql = 'UPDATE `*PREFIX*music_albums`
326
				SET `cover_file_id` = ?
327
				WHERE `id` = ?';
328
		$params = [$coverFileId, $albumId];
329
		$this->execute($sql, $params);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

329
		/** @scrutinizer ignore-deprecated */ $this->execute($sql, $params);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
330
	}
331
332
	/**
333
	 * @param integer[] $coverFileIds
334
	 * @param string[]|null $userIds the users whose music library is targeted; all users are targeted if omitted
335
	 * @return Album[] albums which got modified (with incomplete data, only id and user are valid),
336
	 *         empty array if none
337
	 */
338
	public function removeCovers(array $coverFileIds, array $userIds=null) : array {
339
		// find albums using the given file as cover
340
		$sql = 'SELECT `id`, `user_id` FROM `*PREFIX*music_albums` WHERE `cover_file_id` IN ' .
341
			$this->questionMarks(\count($coverFileIds));
342
		$params = $coverFileIds;
343
		if ($userIds !== null) {
344
			$sql .= ' AND `user_id` IN ' . $this->questionMarks(\count($userIds));
345
			$params = \array_merge($params, $userIds);
346
		}
347
		$albums = $this->findEntities($sql, $params);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...dMapper::findEntities() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

347
		$albums = /** @scrutinizer ignore-deprecated */ $this->findEntities($sql, $params);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
348
349
		// if any albums found, remove the cover from those
350
		$count = \count($albums);
351
		if ($count) {
352
			$sql = 'UPDATE `*PREFIX*music_albums`
353
				SET `cover_file_id` = NULL
354
				WHERE `id` IN ' . $this->questionMarks($count);
355
			$params = Util::extractIds($albums);
356
			$this->execute($sql, $params);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

356
			/** @scrutinizer ignore-deprecated */ $this->execute($sql, $params);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
357
		}
358
359
		return $albums;
360
	}
361
362
	/**
363
	 * @param string|null $userId target user; omit to target all users
364
	 * @return array of dictionaries with keys [albumId, userId, parentFolderId]
365
	 */
366
	public function getAlbumsWithoutCover(string $userId = null) : array {
367
		$sql = 'SELECT DISTINCT `albums`.`id`, `albums`.`user_id`, `files`.`parent`
368
				FROM `*PREFIX*music_albums` `albums`
369
				JOIN `*PREFIX*music_tracks` `tracks` ON `albums`.`id` = `tracks`.`album_id`
370
				JOIN `*PREFIX*filecache` `files` ON `tracks`.`file_id` = `files`.`fileid`
371
				WHERE `albums`.`cover_file_id` IS NULL';
372
		$params = [];
373
		if ($userId !== null) {
374
			$sql .= ' AND `albums`.`user_id` = ?';
375
			$params[] = $userId;
376
		}
377
		$result = $this->execute($sql, $params);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

377
		$result = /** @scrutinizer ignore-deprecated */ $this->execute($sql, $params);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
378
		$return = [];
379
		while ($row = $result->fetch()) {
380
			$return[] = [
381
				'albumId' => (int)$row['id'],
382
				'userId' => $row['user_id'],
383
				'parentFolderId' => (int)$row['parent']
384
			];
385
		}
386
		return $return;
387
	}
388
389
	/**
390
	 * @return boolean True if a cover image was found and added for the album
391
	 */
392
	public function findAlbumCover(int $albumId, int $parentFolderId) : bool {
393
		$return = false;
394
		$imagesSql = 'SELECT `fileid`, `name`
395
					FROM `*PREFIX*filecache`
396
					JOIN `*PREFIX*mimetypes` ON `*PREFIX*mimetypes`.`id` = `*PREFIX*filecache`.`mimetype`
397
					WHERE `parent` = ? AND `*PREFIX*mimetypes`.`mimetype` LIKE \'image%\'';
398
		$params = [$parentFolderId];
399
		$result = $this->execute($imagesSql, $params);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

399
		$result = /** @scrutinizer ignore-deprecated */ $this->execute($imagesSql, $params);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
400
		$images = $result->fetchAll();
401
		if (\count($images) > 0) {
402
			$getImageRank = function($imageName) {
403
				$coverNames = ['cover', 'albumart', 'album', 'front', 'folder'];
404
				foreach ($coverNames as $i => $coverName) {
405
					if (Util::startsWith($imageName, $coverName, /*$ignoreCase=*/true)) {
406
						return $i;
407
					}
408
				}
409
				return \count($coverNames);
410
			};
411
412
			\usort($images, function ($imageA, $imageB) use ($getImageRank) {
413
				return $getImageRank($imageA['name']) <=> $getImageRank($imageB['name']);
414
			});
415
			$imageId = (int)$images[0]['fileid'];
416
			$this->setCover($imageId, $albumId);
417
			$return = true;
418
		}
419
		return $return;
420
	}
421
422
	/**
423
	 * Given an array of track IDs, find corresponding uniqu album IDs, including only
424
	 * those album which have a cover art set.
425
	 * @param int[] $trackIds
426
	 * @return Album[]
427
	 */
428
	public function findAlbumsWithCoversForTracks(array $trackIds, string $userId, int $limit) : array {
429
		$sql = 'SELECT DISTINCT `albums`.*
430
				FROM `*PREFIX*music_albums` `albums`
431
				JOIN `*PREFIX*music_tracks` `tracks` ON `albums`.`id` = `tracks`.`album_id`
432
				WHERE `albums`.`cover_file_id` IS NOT NULL
433
				AND `albums`.`user_id` = ?
434
				AND `tracks`.`id` IN ' . $this->questionMarks(\count($trackIds));
435
		$params = \array_merge([$userId], $trackIds);
436
437
		return $this->findEntities($sql, $params, $limit);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...dMapper::findEntities() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

437
		return /** @scrutinizer ignore-deprecated */ $this->findEntities($sql, $params, $limit);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
438
	}
439
440
	/**
441
	 * Returns the count of albums where the given Artist is featured in
442
	 * @param integer $artistId
443
	 * @return integer
444
	 */
445
	public function countByArtist(int $artistId) : int {
446
		$sql = 'SELECT COUNT(*) AS count FROM (
447
					SELECT DISTINCT `track`.`album_id`
448
					FROM `*PREFIX*music_tracks` `track`
449
					WHERE `track`.`artist_id` = ?
450
						UNION
451
					SELECT `album`.`id`
452
					FROM `*PREFIX*music_albums` `album`
453
					WHERE `album`.`album_artist_id` = ?
454
				) tmp';
455
		$params = [$artistId, $artistId];
456
		$result = $this->execute($sql, $params);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

456
		$result = /** @scrutinizer ignore-deprecated */ $this->execute($sql, $params);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
457
		$row = $result->fetch();
458
		return (int)$row['count'];
459
	}
460
461
	/**
462
	 * Returns the count of albums where the given artist is the album artist
463
	 * @param integer $artistId
464
	 * @return integer
465
	 */
466
	public function countByAlbumArtist(int $artistId) : int {
467
		$sql = 'SELECT COUNT(*) AS count
468
				FROM `*PREFIX*music_albums` `album`
469
				WHERE `album`.`album_artist_id` = ?';
470
		$params = [$artistId];
471
		$result = $this->execute($sql, $params);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

471
		$result = /** @scrutinizer ignore-deprecated */ $this->execute($sql, $params);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
472
		$row = $result->fetch();
473
		return (int)$row['count'];
474
	}
475
476
	/**
477
	 * Overridden from the base implementation to provide support for table-specific rules
478
	 *
479
	 * {@inheritdoc}
480
	 * @see BaseMapper::advFormatSqlCondition()
481
	 */
482
	protected function advFormatSqlCondition(string $rule, string $sqlOp) : string {
483
		// The extra subquery "mysqlhack" seen around some nested queries is needed in order for these to not be insanely slow on MySQL.
484
		// In case of 'recent_played', the MySQL 5.5.62 errored with "1235 This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'" without the extra subquery.
485
		switch ($rule) {
486
			case 'artist':			return "LOWER(`artist`.`name`) $sqlOp LOWER(?)";
487
			case 'song_artist':		return "`*PREFIX*music_albums`.`id` IN (SELECT `album_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*music_artists` `ar` ON `t`.`artist_id` = `ar`.`id` WHERE LOWER(`ar`.`name`) $sqlOp LOWER(?))";
488
			case 'song':			return "`*PREFIX*music_albums`.`id` IN (SELECT `album_id` FROM `*PREFIX*music_tracks` `t` WHERE LOWER(`t`.`title`) $sqlOp LOWER(?))";
489
			case 'year':			// fall through, we only have one kind of year
490
			case 'original_year':	return "`*PREFIX*music_albums`.`id` IN (SELECT * FROM (SELECT `album_id` FROM `*PREFIX*music_tracks` GROUP BY `album_id` HAVING MIN(`year`) $sqlOp ?) mysqlhack)";
491
			case 'songrating':		return "`*PREFIX*music_albums`.`id` IN (SELECT `album_id` FROM `*PREFIX*music_tracks` `t` WHERE `t`.`rating` $sqlOp ?)";
492
			case 'artistrating':	return "`artist`.rating $sqlOp ?";
493
			case 'played_times':	return "`*PREFIX*music_albums`.`id` IN (SELECT * FROM (SELECT `album_id` from `*PREFIX*music_tracks` GROUP BY `album_id` HAVING SUM(`play_count`) $sqlOp ?) mysqlhack)";
494
			case 'last_play':		return "`*PREFIX*music_albums`.`id` IN (SELECT * FROM (SELECT `album_id` from `*PREFIX*music_tracks` GROUP BY `album_id` HAVING MAX(`last_played`) $sqlOp ?) mysqlhack)";
495
			case 'played':			// fall through, we give no access to other people's data; not part of the API spec but Ample uses this
496
			case 'myplayed':		return "`*PREFIX*music_albums`.`id` IN (SELECT * FROM (SELECT `album_id` from `*PREFIX*music_tracks` GROUP BY `album_id` HAVING MAX(`last_played`) $sqlOp) mysqlhack)"; // operator "IS NULL" or "IS NOT NULL"
497
			case 'myplayedartist':	return "`album_artist_id` IN (SELECT * FROM (SELECT `artist_id` from `*PREFIX*music_tracks` GROUP BY `artist_id` HAVING MAX(`last_played`) $sqlOp) mysqlhack)"; // operator "IS NULL" or "IS NOT NULL"
498
			case 'song_count':		return "`*PREFIX*music_albums`.`id` IN (SELECT * FROM (SELECT `album_id` FROM `*PREFIX*music_tracks` GROUP BY `album_id` HAVING COUNT(`id`) $sqlOp ?) mysqlhack)";
499
			case 'time':			return "`*PREFIX*music_albums`.`id` IN (SELECT * FROM (SELECT `album_id` FROM `*PREFIX*music_tracks` GROUP BY `album_id` HAVING SUM(`length`) $sqlOp ?) mysqlhack)";
500
			case 'genre':			// fall through, alias
501
			case 'album_genre':		return "`*PREFIX*music_albums`.`id` IN (SELECT * FROM (SELECT `album_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*music_genres` `g` ON `t`.`genre_id` = `g`.`id` GROUP BY `album_id` HAVING LOWER(GROUP_CONCAT(`g`.`name`)) $sqlOp LOWER(?)) mysqlhack)"; // GROUP_CONCAT not available on PostgreSQL
502
			case 'song_genre':		return "`*PREFIX*music_albums`.`id` IN (SELECT `album_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*music_genres` `g` ON `t`.`genre_id` = `g`.`id` WHERE LOWER(`g`.`name`) $sqlOp LOWER(?))";
503
			case 'no_genre':		return "`*PREFIX*music_albums`.`id` IN (SELECT `album_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*music_genres` `g` ON `t`.`genre_id` = `g`.`id` WHERE `g`.`name` " . (($sqlOp == 'IS NOT NULL') ? '=' : '!=') . ' "")';
504
			case 'playlist':		return "`*PREFIX*music_albums`.`id` IN (SELECT `album_id` FROM `*PREFIX*music_tracks` `t` WHERE $sqlOp EXISTS (SELECT 1 from `*PREFIX*music_playlists` `p` WHERE `p`.`id` = ? AND `p`.`track_ids` LIKE CONCAT('%|',`t`.`id`, '|%')))";
505
			case 'playlist_name':	return "`*PREFIX*music_albums`.`id` IN (SELECT `album_id` FROM `*PREFIX*music_tracks` `t` WHERE EXISTS (SELECT 1 from `*PREFIX*music_playlists` `p` WHERE `p`.`name` $sqlOp ? AND `p`.`track_ids` LIKE CONCAT('%|',`t`.`id`, '|%')))";
506
			case 'file':			return "`*PREFIX*music_albums`.`id` IN (SELECT `album_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*filecache` `f` ON `t`.`file_id` = `f`.`fileid` WHERE LOWER(`f`.`name`) $sqlOp LOWER(?))";
507
			case 'recent_played':	return "`*PREFIX*music_albums`.`id` IN (SELECT `album_id` FROM (SELECT `album_id`, MAX(`last_played`) FROM `*PREFIX*music_tracks` WHERE `user_id` = ? GROUP BY `album_id` ORDER BY MAX(`last_played`) DESC LIMIT $sqlOp) mysqlhack)";
508
			case 'mbid_album':		return parent::advFormatSqlCondition('mbid', $sqlOp); // alias
509
			case 'mbid_song':		return "`*PREFIX*music_albums`.`id` IN (SELECT `album_id` FROM `*PREFIX*music_tracks` `t` WHERE `t`.`mbid` $sqlOp ?)";
510
			case 'mbid_artist':		return "`artist`.`mbid` $sqlOp ?";
511
			case 'has_image':		return "`*PREFIX*music_albums`.`cover_file_id` $sqlOp"; // operator "IS NULL" or "IS NOT NULL"
512
			default:				return parent::advFormatSqlCondition($rule, $sqlOp);
513
		}
514
	}
515
516
	/**
517
	 * {@inheritdoc}
518
	 * @see \OCA\Music\Db\BaseMapper::findUniqueEntity()
519
	 * @param Album $album
520
	 * @return Album
521
	 */
522
	protected function findUniqueEntity(Entity $album) : Entity {
523
		$sql = $this->selectUserEntities('`*PREFIX*music_albums`.`hash` = ?');
524
		return $this->findEntity($sql, [$album->getUserId(), $album->getHash()]);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->findEntity...(), $album->getHash())) returns the type OCP\AppFramework\Db\Entity which includes types incompatible with the type-hinted return OCA\Music\Db\Entity.
Loading history...
Bug introduced by
The method getHash() does not exist on OCA\Music\Db\Entity. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

524
		return $this->findEntity($sql, [$album->getUserId(), $album->/** @scrutinizer ignore-call */ getHash()]);
Loading history...
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...oudMapper::findEntity() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

524
		return /** @scrutinizer ignore-deprecated */ $this->findEntity($sql, [$album->getUserId(), $album->getHash()]);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
525
	}
526
}
527