AlbumMapper::findAllByNameRecursive()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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

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

84
		while ($row = /** @scrutinizer ignore-deprecated */ $result->fetch()) {

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...
85
			$artistIds[$row['album_id']][] = (int)$row['artist_id'];
86
		}
87
		$result->closeCursor();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::closeCursor() has been deprecated: 21.0.0 use \OCP\DB\IResult::closeCursor on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

87
		/** @scrutinizer ignore-deprecated */ $result->closeCursor();

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...
88
		return $artistIds;
89
	}
90
91
	/**
92
	 * returns release years mapped to album IDs
93
	 *
94
	 * @param integer[]|null $albumIds IDs of the albums; get all albums of the user if null given
95
	 * @param string $userId the user ID
96
	 * @return array<int, int[]> keys are albums IDs and values are arrays of years
97
	 */
98
	public function getYearsByAlbumId(?array $albumIds, string $userId) : array {
99
		$sql = 'SELECT DISTINCT `track`.`album_id`, `track`.`year`
100
				FROM `*PREFIX*music_tracks` `track`
101
				WHERE `track`.`user_id` = ?
102
				AND `track`.`year` IS NOT NULL ';
103
		$params = [$userId];
104
105
		if ($albumIds !== null) {
0 ignored issues
show
introduced by
The condition $albumIds !== null is always true.
Loading history...
106
			$sql .= 'AND `track`.`album_id` IN ' . $this->questionMarks(\count($albumIds));
107
			$params = \array_merge($params, $albumIds);
108
		}
109
110
		$result = $this->execute($sql, $params);
111
		$years = [];
112
		while ($row = $result->fetch()) {
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::fetch() has been deprecated: 21.0.0 use \OCP\DB\IResult::fetch on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

112
		while ($row = /** @scrutinizer ignore-deprecated */ $result->fetch()) {

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...
113
			$years[$row['album_id']][] = (int)$row['year'];
114
		}
115
		$result->closeCursor();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::closeCursor() has been deprecated: 21.0.0 use \OCP\DB\IResult::closeCursor on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

115
		/** @scrutinizer ignore-deprecated */ $result->closeCursor();

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...
116
		return $years;
117
	}
118
119
	/**
120
	 * returns genres mapped to album IDs
121
	 *
122
	 * @param integer[]|null $albumIds IDs of the albums; get all albums of the user if null given
123
	 * @param string $userId the user ID
124
	 * @return array<int, Genre[]> keys are albums IDs and values are arrays of *partial* Genre objects (only id and name properties set)
125
	 */
126
	public function getGenresByAlbumId(?array $albumIds, string $userId) : array {
127
		$sql = 'SELECT DISTINCT `album_id`, `genre_id`, `*PREFIX*music_genres`.`name` AS `genre_name`
128
				FROM `*PREFIX*music_tracks`
129
				LEFT JOIN `*PREFIX*music_genres`
130
				ON `genre_id` = `*PREFIX*music_genres`.`id`
131
				WHERE `*PREFIX*music_tracks`.`user_id` = ?
132
				AND `genre_id` IS NOT NULL ';
133
		$params = [$userId];
134
135
		if ($albumIds !== null) {
0 ignored issues
show
introduced by
The condition $albumIds !== null is always true.
Loading history...
136
			$sql .= 'AND `album_id` IN ' . $this->questionMarks(\count($albumIds));
137
			$params = \array_merge($params, $albumIds);
138
		}
139
140
		$result = $this->execute($sql, $params);
141
		$genres = [];
142
		while ($row = $result->fetch()) {
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::fetch() has been deprecated: 21.0.0 use \OCP\DB\IResult::fetch on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

142
		while ($row = /** @scrutinizer ignore-deprecated */ $result->fetch()) {

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...
143
			$genre = new Genre();
144
			$genre->setUserId($userId);
145
			$genre->setId((int)$row['genre_id']);
146
			$genre->setName($row['genre_name']);
147
			$genres[$row['album_id']][] = $genre;
148
		}
149
		$result->closeCursor();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::closeCursor() has been deprecated: 21.0.0 use \OCP\DB\IResult::closeCursor on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

149
		/** @scrutinizer ignore-deprecated */ $result->closeCursor();

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...
150
		return $genres;
151
	}
152
153
	/**
154
	 * returns number of disks per album ID
155
	 *
156
	 * @param integer[]|null $albumIds IDs of the albums; get all albums of the user if null given
157
	 * @param string $userId the user ID
158
	 * @return array<int, int> keys are albums IDs and values are disk counts
159
	 */
160
	public function getDiscCountByAlbumId(?array $albumIds, string $userId) : array {
161
		$sql = 'SELECT `album_id`, MAX(`disk`) AS `disc_count`
162
				FROM `*PREFIX*music_tracks`
163
				WHERE `user_id` = ?
164
				GROUP BY `album_id` ';
165
		$params = [$userId];
166
167
		if ($albumIds !== null) {
0 ignored issues
show
introduced by
The condition $albumIds !== null is always true.
Loading history...
168
			$sql .= 'HAVING `album_id` IN ' . $this->questionMarks(\count($albumIds));
169
			$params = \array_merge($params, $albumIds);
170
		}
171
172
		$result = $this->execute($sql, $params);
173
		$diskCountByAlbum = [];
174
		while ($row = $result->fetch()) {
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::fetch() has been deprecated: 21.0.0 use \OCP\DB\IResult::fetch on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

174
		while ($row = /** @scrutinizer ignore-deprecated */ $result->fetch()) {

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...
175
			$diskCountByAlbum[$row['album_id']] = (int)$row['disc_count'];
176
		}
177
		$result->closeCursor();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::closeCursor() has been deprecated: 21.0.0 use \OCP\DB\IResult::closeCursor on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

177
		/** @scrutinizer ignore-deprecated */ $result->closeCursor();

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...
178
		return $diskCountByAlbum;
179
	}
180
181
	/**
182
	 * returns summed track play counts of each album of the user, omitting albums which have never been played
183
	 *
184
	 * @return array<int, int> keys are album IDs and values are play count sums; ordered largest counts first
185
	 */
186
	public function getAlbumTracksPlayCount(string $userId, ?int $limit=null, ?int $offset=null) : array {
187
		$sql = 'SELECT `album_id`, SUM(`play_count`) AS `sum_count`
188
				FROM `*PREFIX*music_tracks`
189
				WHERE `user_id` = ? AND `play_count` > 0
190
				GROUP BY `album_id`
191
				ORDER BY `sum_count` DESC, `album_id`'; // the second criterion is just to make the order predictable on even counts
192
193
		$result = $this->execute($sql, [$userId], $limit, $offset);
194
		$playCountByAlbum = [];
195
		while ($row = $result->fetch()) {
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::fetch() has been deprecated: 21.0.0 use \OCP\DB\IResult::fetch on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

195
		while ($row = /** @scrutinizer ignore-deprecated */ $result->fetch()) {

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...
196
			$playCountByAlbum[$row['album_id']] = (int)$row['sum_count'];
197
		}
198
		$result->closeCursor();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::closeCursor() has been deprecated: 21.0.0 use \OCP\DB\IResult::closeCursor on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

198
		/** @scrutinizer ignore-deprecated */ $result->closeCursor();

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...
199
		return $playCountByAlbum;
200
	}
201
202
	/**
203
	 * returns the latest play time of each album of the user, omitting albums which have never been played
204
	 *
205
	 * @return array<int, string> keys are album IDs and values are date-times; ordered latest times first
206
	 */
207
	public function getLatestAlbumPlayTimes(string $userId, ?int $limit=null, ?int $offset=null) : array {
208
		$sql = 'SELECT `album_id`, MAX(`last_played`) AS `latest_time`
209
				FROM `*PREFIX*music_tracks`
210
				WHERE `user_id` = ? AND `last_played` IS NOT NULL
211
				GROUP BY `album_id`
212
				ORDER BY `latest_time` DESC';
213
214
		$result = $this->execute($sql, [$userId], $limit, $offset);
215
		$latestTimeByAlbum = [];
216
		while ($row = $result->fetch()) {
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::fetch() has been deprecated: 21.0.0 use \OCP\DB\IResult::fetch on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

216
		while ($row = /** @scrutinizer ignore-deprecated */ $result->fetch()) {

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...
217
			$latestTimeByAlbum[$row['album_id']] = $row['latest_time'];
218
		}
219
		$result->closeCursor();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::closeCursor() has been deprecated: 21.0.0 use \OCP\DB\IResult::closeCursor on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

219
		/** @scrutinizer ignore-deprecated */ $result->closeCursor();

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...
220
		return $latestTimeByAlbum;
221
	}
222
223
	/**
224
	 * returns the latest play time of each album of the user, including albums which have never been played
225
	 *
226
	 * @return array<int, ?string> keys are album IDs and values are date-times (or null for never played);
227
	 *								ordered furthest times first
228
	 */
229
	public function getFurthestAlbumPlayTimes(string $userId, ?int $limit=null, ?int $offset=null) : array {
230
		$sql = 'SELECT `album_id`, MAX(`last_played`) AS `latest_time`
231
				FROM `*PREFIX*music_tracks`
232
				WHERE `user_id` = ?
233
				GROUP BY `album_id`
234
				ORDER BY `latest_time` ASC';
235
236
		$result = $this->execute($sql, [$userId], $limit, $offset);
237
		$latestTimeByAlbum = [];
238
		while ($row = $result->fetch()) {
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::fetch() has been deprecated: 21.0.0 use \OCP\DB\IResult::fetch on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

238
		while ($row = /** @scrutinizer ignore-deprecated */ $result->fetch()) {

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...
239
			$latestTimeByAlbum[$row['album_id']] = $row['latest_time'];
240
		}
241
		$result->closeCursor();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::closeCursor() has been deprecated: 21.0.0 use \OCP\DB\IResult::closeCursor on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

241
		/** @scrutinizer ignore-deprecated */ $result->closeCursor();

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...
242
		return $latestTimeByAlbum;
243
	}
244
245
	/**
246
	 * @return Album[]
247
	 */
248
	public function findAllByNameRecursive(string $name, string $userId, ?int $limit=null, ?int $offset=null) : array {
249
		$condition = '( LOWER(`artist`.`name`) LIKE LOWER(?) OR
250
						LOWER(`*PREFIX*music_albums`.`name`) LIKE LOWER(?) )';
251
		$sql = $this->selectUserEntities($condition, 'ORDER BY LOWER(`*PREFIX*music_albums`.`name`)');
252
		$name = BaseMapper::prepareSubstringSearchPattern($name);
253
		$params = [$userId, $name, $name];
254
		return $this->findEntities($sql, $params, $limit, $offset);
255
	}
256
257
	/**
258
	 * returns albums of a specified artist
259
	 * The artist may be an album_artist or the artist of a track
260
	 *
261
	 * @param integer $artistId
262
	 * @return Album[]
263
	 */
264
	public function findAllByArtist(int $artistId, string $userId, ?int $limit=null, ?int $offset=null) : array {
265
		$sql = $this->selectEntities(
266
				'`*PREFIX*music_albums`.`id` IN (
267
					SELECT DISTINCT `album`.`id`
268
					FROM `*PREFIX*music_albums` `album`
269
					WHERE `album`.`album_artist_id` = ?
270
						UNION
271
					SELECT DISTINCT `track`.`album_id`
272
					FROM `*PREFIX*music_tracks` `track`
273
					WHERE `track`.`artist_id` = ?
274
				) AND `*PREFIX*music_albums`.`user_id` = ?',
275
				'ORDER BY LOWER(`*PREFIX*music_albums`.`name`)');
276
		$params = [$artistId, $artistId, $userId];
277
		return $this->findEntities($sql, $params, $limit, $offset);
278
	}
279
280
	/**
281
	 * returns albums of a specified artists
282
	 * The artist must be album_artist on the album, artists of individual tracks are not considered
283
	 *
284
	 * @param int[] $artistIds
285
	 * @return Album[]
286
	 */
287
	public function findAllByAlbumArtist(array $artistIds, string $userId, ?int $limit=null, ?int $offset=null) : array {
288
		$sql = $this->selectUserEntities('`album_artist_id` IN ' . $this->questionMarks(\count($artistIds)));
289
		$params = \array_merge([$userId], $artistIds);
290
		return $this->findEntities($sql, $params, $limit, $offset);
291
	}
292
293
	/**
294
	 * @return Album[]
295
	 */
296
	public function findAllByGenre(int $genreId, string $userId, ?int $limit=null, ?int $offset=null) : array {
297
		$sql = $this->selectUserEntities('EXISTS '.
298
				'(SELECT 1 FROM `*PREFIX*music_tracks` `track`
299
				  WHERE `*PREFIX*music_albums`.`id` = `track`.`album_id`
300
				  AND `track`.`genre_id` = ?)');
301
302
		$params = [$userId, $genreId];
303
		return $this->findEntities($sql, $params, $limit, $offset);
304
	}
305
306
	/**
307
	 * @return boolean True if one or more albums were influenced
308
	 */
309
	public function updateFolderCover(int $coverFileId, int $folderId) : bool {
310
		$sql = 'SELECT DISTINCT `tracks`.`album_id`
311
				FROM `*PREFIX*music_tracks` `tracks`
312
				JOIN `*PREFIX*filecache` `files` ON `tracks`.`file_id` = `files`.`fileid`
313
				WHERE `files`.`parent` = ?';
314
		$params = [$folderId];
315
		$result = $this->execute($sql, $params);
316
		$albumIds = $result->fetchAll(\PDO::FETCH_COLUMN);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::fetchAll() has been deprecated: 21.0.0 use \OCP\DB\IResult::fetchAll on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

316
		$albumIds = /** @scrutinizer ignore-deprecated */ $result->fetchAll(\PDO::FETCH_COLUMN);

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...
317
		$result->closeCursor();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::closeCursor() has been deprecated: 21.0.0 use \OCP\DB\IResult::closeCursor on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

317
		/** @scrutinizer ignore-deprecated */ $result->closeCursor();

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...
318
319
		$updated = false;
320
		if (\count($albumIds) > 0) {
321
			$sql = 'UPDATE `*PREFIX*music_albums`
322
					SET `cover_file_id` = ?
323
					WHERE `cover_file_id` IS NULL AND `id` IN '. $this->questionMarks(\count($albumIds));
324
			$params = \array_merge([$coverFileId], $albumIds);
325
			$result = $this->execute($sql, $params);
326
			$updated = $result->rowCount() > 0;
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::rowCount() has been deprecated: 21.0.0 use \OCP\DB\IResult::rowCount on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

326
			$updated = /** @scrutinizer ignore-deprecated */ $result->rowCount() > 0;

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...
327
			$result->closeCursor();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::closeCursor() has been deprecated: 21.0.0 use \OCP\DB\IResult::closeCursor on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

327
			/** @scrutinizer ignore-deprecated */ $result->closeCursor();

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...
328
		}
329
330
		return $updated;
331
	}
332
333
	/**
334
	 * Set file ID to be used as cover for an album
335
	 */
336
	public function setCover(?int $coverFileId, int $albumId) : void {
337
		$sql = 'UPDATE `*PREFIX*music_albums`
338
				SET `cover_file_id` = ?
339
				WHERE `id` = ?';
340
		$params = [$coverFileId, $albumId];
341
		$result = $this->execute($sql, $params);
342
		$result->closeCursor();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::closeCursor() has been deprecated: 21.0.0 use \OCP\DB\IResult::closeCursor on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

342
		/** @scrutinizer ignore-deprecated */ $result->closeCursor();

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...
343
	}
344
345
	/**
346
	 * @param integer[] $coverFileIds
347
	 * @param string[]|null $userIds the users whose music library is targeted; all users are targeted if omitted
348
	 * @return Album[] albums which got modified (with incomplete data, only id and user are valid),
349
	 *         empty array if none
350
	 */
351
	public function removeCovers(array $coverFileIds, ?array $userIds=null) : array {
352
		// find albums using the given file as cover
353
		$sql = 'SELECT `id`, `user_id` FROM `*PREFIX*music_albums` WHERE `cover_file_id` IN ' .
354
			$this->questionMarks(\count($coverFileIds));
355
		$params = $coverFileIds;
356
		if ($userIds !== null) {
357
			$sql .= ' AND `user_id` IN ' . $this->questionMarks(\count($userIds));
358
			$params = \array_merge($params, $userIds);
359
		}
360
		$albums = $this->findEntities($sql, $params);
361
362
		// if any albums found, remove the cover from those
363
		$count = \count($albums);
364
		if ($count) {
365
			$sql = 'UPDATE `*PREFIX*music_albums`
366
				SET `cover_file_id` = NULL
367
				WHERE `id` IN ' . $this->questionMarks($count);
368
			$params = ArrayUtil::extractIds($albums);
369
			$result = $this->execute($sql, $params);
370
			$result->closeCursor();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::closeCursor() has been deprecated: 21.0.0 use \OCP\DB\IResult::closeCursor on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

370
			/** @scrutinizer ignore-deprecated */ $result->closeCursor();

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...
371
		}
372
373
		return $albums;
374
	}
375
376
	/**
377
	 * @param string|null $userId target user; omit to target all users
378
	 * @param ?int[] $parentIds limit search to albums having tracks in these folders; omit for no limit
379
	 * @return array<array{albumId: int, userId: string, parentFolderId: int}>
380
	 */
381
	public function getAlbumsWithoutCover(?string $userId = null, ?array $parentIds = null) : array {
382
		$sql = 'SELECT DISTINCT `albums`.`id`, `albums`.`user_id`, `files`.`parent`
383
				FROM `*PREFIX*music_albums` `albums`
384
				JOIN `*PREFIX*music_tracks` `tracks` ON `albums`.`id` = `tracks`.`album_id`
385
				JOIN `*PREFIX*filecache` `files` ON `tracks`.`file_id` = `files`.`fileid`
386
				WHERE `albums`.`cover_file_id` IS NULL';
387
		$params = [];
388
		if ($userId !== null) {
389
			$sql .= ' AND `albums`.`user_id` = ?';
390
			$params[] = $userId;
391
		}
392
		if (!empty($parentIds)) {
393
			$sql .= ' AND `files`.`parent` IN ' . $this->questionMarks(\count($parentIds));
394
			$params = \array_merge($params, $parentIds);
395
		}
396
397
		$result = $this->execute($sql, $params);
398
		$return = [];
399
		while ($row = $result->fetch()) {
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::fetch() has been deprecated: 21.0.0 use \OCP\DB\IResult::fetch on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

399
		while ($row = /** @scrutinizer ignore-deprecated */ $result->fetch()) {

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
			$return[] = [
401
				'albumId' => (int)$row['id'],
402
				'userId' => $row['user_id'],
403
				'parentFolderId' => (int)$row['parent']
404
			];
405
		}
406
		$result->closeCursor();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::closeCursor() has been deprecated: 21.0.0 use \OCP\DB\IResult::closeCursor on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

406
		/** @scrutinizer ignore-deprecated */ $result->closeCursor();

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...
407
408
		return $return;
409
	}
410
411
	/**
412
	 * @return boolean True if a cover image was found and added for the album
413
	 */
414
	public function findAlbumCover(int $albumId, int $parentFolderId) : bool {
415
		$return = false;
416
		$imagesSql = 'SELECT `fileid`, `name`
417
					FROM `*PREFIX*filecache`
418
					JOIN `*PREFIX*mimetypes` ON `*PREFIX*mimetypes`.`id` = `*PREFIX*filecache`.`mimetype`
419
					WHERE `parent` = ? AND `*PREFIX*mimetypes`.`mimetype` LIKE \'image%\'';
420
		$params = [$parentFolderId];
421
		$result = $this->execute($imagesSql, $params);
422
		$images = $result->fetchAll();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::fetchAll() has been deprecated: 21.0.0 use \OCP\DB\IResult::fetchAll on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

422
		$images = /** @scrutinizer ignore-deprecated */ $result->fetchAll();

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...
423
		$result->closeCursor();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::closeCursor() has been deprecated: 21.0.0 use \OCP\DB\IResult::closeCursor on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

423
		/** @scrutinizer ignore-deprecated */ $result->closeCursor();

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...
424
		if (\count($images) > 0) {
425
			$getImageRank = function($imageName) {
426
				$coverNames = ['cover', 'albumart', 'album', 'front', 'folder'];
427
				foreach ($coverNames as $i => $coverName) {
428
					if (StringUtil::startsWith($imageName, $coverName, /*$ignoreCase=*/true)) {
429
						return $i;
430
					}
431
				}
432
				return \count($coverNames);
433
			};
434
435
			\usort($images, fn($imageA, $imageB) =>
436
				$getImageRank($imageA['name']) <=> $getImageRank($imageB['name'])
437
			);
438
			$imageId = (int)$images[0]['fileid'];
439
			$this->setCover($imageId, $albumId);
440
			$return = true;
441
		}
442
		return $return;
443
	}
444
445
	/**
446
	 * Given an array of track IDs, find corresponding unique album IDs, including only
447
	 * those album which have a cover art set.
448
	 * @param int[] $trackIds
449
	 * @return Album[]
450
	 */
451
	public function findAlbumsWithCoversForTracks(array $trackIds, string $userId, int $limit) : array {
452
		$sql = 'SELECT DISTINCT `albums`.*
453
				FROM `*PREFIX*music_albums` `albums`
454
				JOIN `*PREFIX*music_tracks` `tracks` ON `albums`.`id` = `tracks`.`album_id`
455
				WHERE `albums`.`cover_file_id` IS NOT NULL
456
				AND `albums`.`user_id` = ?
457
				AND `tracks`.`id` IN ' . $this->questionMarks(\count($trackIds));
458
		$params = \array_merge([$userId], $trackIds);
459
460
		return $this->findEntities($sql, $params, $limit);
461
	}
462
463
	/**
464
	 * Returns the count of albums where the given Artist is featured in
465
	 * @param integer $artistId
466
	 * @return integer
467
	 */
468
	public function countByArtist(int $artistId) : int {
469
		$sql = 'SELECT COUNT(*) AS count FROM (
470
					SELECT DISTINCT `track`.`album_id`
471
					FROM `*PREFIX*music_tracks` `track`
472
					WHERE `track`.`artist_id` = ?
473
						UNION
474
					SELECT `album`.`id`
475
					FROM `*PREFIX*music_albums` `album`
476
					WHERE `album`.`album_artist_id` = ?
477
				) tmp';
478
		$params = [$artistId, $artistId];
479
		$result = $this->execute($sql, $params);
480
		$row = $result->fetch();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::fetch() has been deprecated: 21.0.0 use \OCP\DB\IResult::fetch on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

480
		$row = /** @scrutinizer ignore-deprecated */ $result->fetch();

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...
481
		$result->closeCursor();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::closeCursor() has been deprecated: 21.0.0 use \OCP\DB\IResult::closeCursor on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

481
		/** @scrutinizer ignore-deprecated */ $result->closeCursor();

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...
482
		return (int)$row['count'];
483
	}
484
485
	/**
486
	 * Returns the count of albums where the given artist is the album artist
487
	 * @param integer $artistId
488
	 * @return integer
489
	 */
490
	public function countByAlbumArtist(int $artistId) : int {
491
		$sql = 'SELECT COUNT(*) AS count
492
				FROM `*PREFIX*music_albums` `album`
493
				WHERE `album`.`album_artist_id` = ?';
494
		$params = [$artistId];
495
		$result = $this->execute($sql, $params);
496
		$row = $result->fetch();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::fetch() has been deprecated: 21.0.0 use \OCP\DB\IResult::fetch on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

496
		$row = /** @scrutinizer ignore-deprecated */ $result->fetch();

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...
497
		$result->closeCursor();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\IPreparedStatement::closeCursor() has been deprecated: 21.0.0 use \OCP\DB\IResult::closeCursor on the \OCP\DB\IResult returned by \OCP\IDBConnection::prepare ( Ignorable by Annotation )

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

497
		/** @scrutinizer ignore-deprecated */ $result->closeCursor();

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...
498
		return (int)$row['count'];
499
	}
500
501
	/**
502
	 * Overridden from the base implementation to provide support for table-specific rules
503
	 *
504
	 * {@inheritdoc}
505
	 * @see BaseMapper::advFormatSqlCondition()
506
	 */
507
	protected function advFormatSqlCondition(string $rule, string $sqlOp, string $conv) : string {
508
		// The extra subquery "mysqlhack" seen around some nested queries is needed in order for these to not be insanely slow on MySQL.
509
		// 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.
510
		$condForRule = [
511
			'album_artist'	=> "$conv(`artist`.`name`) $sqlOp $conv(?)",
512
			'song_artist'	=> "`*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 $conv(`ar`.`name`) $sqlOp $conv(?))",
513
			'song'			=> "`*PREFIX*music_albums`.`id` IN (SELECT `album_id` FROM `*PREFIX*music_tracks` `t` WHERE $conv(`t`.`title`) $sqlOp $conv(?))",
514
			'original_year'	=> "`*PREFIX*music_albums`.`id` IN (SELECT * FROM (SELECT `album_id` FROM `*PREFIX*music_tracks` GROUP BY `album_id` HAVING MIN(`year`) $sqlOp ?) mysqlhack)",
515
			'songrating'	=> "`*PREFIX*music_albums`.`id` IN (SELECT `album_id` FROM `*PREFIX*music_tracks` `t` WHERE `t`.`rating` $sqlOp ?)",
516
			'artistrating'	=> "`artist`.rating $sqlOp ?",
517
			'played_times'	=> "`*PREFIX*music_albums`.`id` IN (SELECT * FROM (SELECT `album_id` from `*PREFIX*music_tracks` GROUP BY `album_id` HAVING SUM(`play_count`) $sqlOp ?) mysqlhack)",
518
			'last_play'		=> "`*PREFIX*music_albums`.`id` IN (SELECT * FROM (SELECT `album_id` from `*PREFIX*music_tracks` GROUP BY `album_id` HAVING MAX(`last_played`) $sqlOp ?) mysqlhack)",
519
			'myplayed'		=> "`*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"
520
			'myplayedartist'=> "`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"
521
			'song_count'	=> "`*PREFIX*music_albums`.`id` IN (SELECT * FROM (SELECT `album_id` FROM `*PREFIX*music_tracks` GROUP BY `album_id` HAVING COUNT(`id`) $sqlOp ?) mysqlhack)",
522
			'disk_count'	=> "`*PREFIX*music_albums`.`id` IN (SELECT * FROM (SELECT `album_id` FROM `*PREFIX*music_tracks` GROUP BY `album_id` HAVING MAX(`disk`) $sqlOp ?) mysqlhack)",
523
			'time'			=> "`*PREFIX*music_albums`.`id` IN (SELECT * FROM (SELECT `album_id` FROM `*PREFIX*music_tracks` GROUP BY `album_id` HAVING SUM(`length`) $sqlOp ?) mysqlhack)",
524
			'album_genre'	=> "`*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 $conv(" . $this->sqlGroupConcat('`g`.`name`') . ") $sqlOp $conv(?)) mysqlhack)",
525
			'song_genre'	=> "`*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 $conv(`g`.`name`) $sqlOp $conv(?))",
526
			'no_genre'		=> "`*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') ? '=' : '!=') . ' "")',
527
			'playlist'		=> "`*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 " . $this->sqlConcat("'%|'", "`t`.`id`", "'|%'") . '))',
528
			'playlist_name'	=> "`*PREFIX*music_albums`.`id` IN (SELECT `album_id` FROM `*PREFIX*music_tracks` `t` WHERE EXISTS (SELECT 1 from `*PREFIX*music_playlists` `p` WHERE $conv(`p`.`name`) $sqlOp $conv(?) AND `p`.`track_ids` LIKE " . $this->sqlConcat("'%|'", "`t`.`id`", "'|%'") . '))',
529
			'file'			=> "`*PREFIX*music_albums`.`id` IN (SELECT `album_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*filecache` `f` ON `t`.`file_id` = `f`.`fileid` WHERE $conv(`f`.`name`) $sqlOp $conv(?))",
530
			'recent_played'	=> "`*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)",
531
			'mbid_song'		=> "`*PREFIX*music_albums`.`id` IN (SELECT `album_id` FROM `*PREFIX*music_tracks` `t` WHERE `t`.`mbid` $sqlOp ?)",
532
			'mbid_artist'	=> "`artist`.`mbid` $sqlOp ?",
533
			'has_image'		=> "`*PREFIX*music_albums`.`cover_file_id` $sqlOp" // operator "IS NULL" or "IS NOT NULL"
534
		];
535
536
		// Add alias rules
537
		$condForRule['year'] = $condForRule['original_year'];	// we only have one kind of year
538
		$condForRule['played'] = $condForRule['myplayed'];		// we give no access to other people's data; not part of the API spec but Ample uses this
539
		$condForRule['artist'] = $condForRule['album_artist'];
540
		$condForRule['genre'] = $condForRule['album_genre'];
541
		$condForRule['album'] = parent::advFormatSqlCondition('title', $sqlOp, $conv);
542
		$condForRule['mbid_album'] = parent::advFormatSqlCondition('mbid', $sqlOp, $conv);
543
544
		return $condForRule[$rule] ?? parent::advFormatSqlCondition($rule, $sqlOp, $conv);
545
	}
546
}
547