| Total Complexity | 91 |
| Total Lines | 539 |
| Duplicated Lines | 0 % |
| Changes | 19 | ||
| Bugs | 0 | Features | 0 |
Complex classes like TrackMapper 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 TrackMapper, and based on these observations, apply Extract Interface, too.
| 1 | <?php declare(strict_types=1); |
||
| 23 | class TrackMapper extends BaseMapper { |
||
| 24 | public function __construct(IDBConnection $db, IConfig $config) { |
||
| 25 | parent::__construct($db, $config, 'music_tracks', Track::class, 'title', 'album_id'); |
||
| 26 | } |
||
| 27 | |||
| 28 | /** |
||
| 29 | * Override the base implementation to include data from multiple tables |
||
| 30 | * |
||
| 31 | * {@inheritdoc} |
||
| 32 | * @see BaseMapper::selectEntities() |
||
| 33 | */ |
||
| 34 | protected function selectEntities(string $condition, string $extension=null) : string { |
||
| 47 | } |
||
| 48 | |||
| 49 | /** |
||
| 50 | * Overridden from the base implementation to add support for sorting by artist, play_count, and last_played. |
||
| 51 | * |
||
| 52 | * {@inheritdoc} |
||
| 53 | * @see BaseMapper::formatSortingClause() |
||
| 54 | */ |
||
| 55 | protected function formatSortingClause(int $sortBy, bool $invertSort = false) : ?string { |
||
| 56 | switch ($sortBy) { |
||
| 57 | case SortBy::Parent: |
||
| 58 | $dir = $invertSort ? 'DESC' : 'ASC'; |
||
| 59 | // Note: the alternative form "LOWER(`artist_name`) wouldn't work on PostgreSQL, see https://github.com/owncloud/music/issues/1046 for a similar case |
||
| 60 | return "ORDER BY LOWER(`artist`.`name`) $dir, LOWER(`title`) $dir"; |
||
| 61 | case SortBy::PlayCount: |
||
| 62 | $dir = $invertSort ? 'ASC' : 'DESC'; |
||
| 63 | return "ORDER BY `play_count` $dir"; |
||
| 64 | case SortBy::LastPlayed: |
||
| 65 | $dir = $invertSort ? 'ASC' : 'DESC'; |
||
| 66 | return "ORDER BY `last_played` $dir"; |
||
| 67 | default: |
||
| 68 | return parent::formatSortingClause($sortBy, $invertSort); |
||
| 69 | } |
||
| 70 | } |
||
| 71 | |||
| 72 | /** |
||
| 73 | * Returns all tracks of the given artist (both album and track artists are considered) |
||
| 74 | * @param int[] $artistIds |
||
| 75 | * @return Track[] |
||
| 76 | */ |
||
| 77 | public function findAllByArtist(array $artistIds, string $userId, ?int $limit=null, ?int $offset=null) : array { |
||
| 78 | $questionMarks = $this->questionMarks(\count($artistIds)); |
||
| 79 | $sql = $this->selectUserEntities( |
||
| 80 | "`artist_id` IN $questionMarks OR `album_id` IN (SELECT `id` from `*PREFIX*music_albums` WHERE `album_artist_id` IN $questionMarks)", |
||
| 81 | 'ORDER BY LOWER(`title`)'); |
||
| 82 | $params = \array_merge([$userId], $artistIds, $artistIds); |
||
| 83 | return $this->findEntities($sql, $params, $limit, $offset); |
||
| 84 | } |
||
| 85 | |||
| 86 | /** |
||
| 87 | * @param int[] $albumIds |
||
| 88 | * @return Track[] |
||
| 89 | */ |
||
| 90 | public function findAllByAlbum(array $albumIds, string $userId, ?int $artistId=null, ?int $limit=null, ?int $offset=null) : array { |
||
| 91 | $condition = '`album_id` IN ' . $this->questionMarks(\count($albumIds)); |
||
| 92 | $params = \array_merge([$userId], $albumIds); |
||
| 93 | |||
| 94 | if ($artistId !== null) { |
||
| 95 | $condition .= ' AND `artist_id` = ? '; |
||
| 96 | $params[] = $artistId; |
||
| 97 | } |
||
| 98 | |||
| 99 | $sql = $this->selectUserEntities($condition, |
||
| 100 | 'ORDER BY `*PREFIX*music_tracks`.`disk`, `number`, LOWER(`title`)'); |
||
| 101 | return $this->findEntities($sql, $params, $limit, $offset); |
||
| 102 | } |
||
| 103 | |||
| 104 | /** |
||
| 105 | * @return Track[] |
||
| 106 | */ |
||
| 107 | public function findAllByFolder(int $folderId, string $userId, ?int $limit=null, ?int $offset=null) : array { |
||
| 111 | } |
||
| 112 | |||
| 113 | /** |
||
| 114 | * @return Track[] |
||
| 115 | */ |
||
| 116 | public function findAllByGenre(int $genreId, string $userId, ?int $limit=null, ?int $offset=null) : array { |
||
| 120 | } |
||
| 121 | |||
| 122 | /** |
||
| 123 | * @return int[] |
||
| 124 | */ |
||
| 125 | public function findAllFileIds(string $userId) : array { |
||
| 126 | $sql = 'SELECT `file_id` FROM `*PREFIX*music_tracks` WHERE `user_id` = ?'; |
||
| 127 | $result = $this->execute($sql, [$userId]); |
||
| 128 | return $result->fetchAll(\PDO::FETCH_COLUMN); |
||
| 129 | } |
||
| 130 | |||
| 131 | /** |
||
| 132 | * Find a track of user matching a file ID |
||
| 133 | * @throws \OCP\AppFramework\Db\DoesNotExistException if not found |
||
| 134 | */ |
||
| 135 | public function findByFileId(int $fileId, string $userId) : Track { |
||
| 136 | $sql = $this->selectUserEntities('`file_id` = ?'); |
||
| 137 | $params = [$userId, $fileId]; |
||
| 138 | return $this->findEntity($sql, $params); |
||
|
|
|||
| 139 | } |
||
| 140 | |||
| 141 | /** |
||
| 142 | * Find tracks of user with multiple file IDs |
||
| 143 | * @param integer[] $fileIds |
||
| 144 | * @param string[] $userIds |
||
| 145 | * @return Track[] |
||
| 146 | */ |
||
| 147 | public function findByFileIds(array $fileIds, array $userIds) : array { |
||
| 148 | $sql = $this->selectEntities( |
||
| 149 | '`*PREFIX*music_tracks`.`user_id` IN ' . $this->questionMarks(\count($userIds)) . |
||
| 150 | ' AND `file_id` IN '. $this->questionMarks(\count($fileIds))); |
||
| 151 | $params = \array_merge($userIds, $fileIds); |
||
| 152 | return $this->findEntities($sql, $params); |
||
| 153 | } |
||
| 154 | |||
| 155 | /** |
||
| 156 | * Finds tracks of all users matching one or multiple file IDs |
||
| 157 | * @param integer[] $fileIds |
||
| 158 | * @return Track[] |
||
| 159 | */ |
||
| 160 | public function findAllByFileIds(array $fileIds) : array { |
||
| 161 | $sql = $this->selectEntities('`file_id` IN '. |
||
| 162 | $this->questionMarks(\count($fileIds))); |
||
| 163 | return $this->findEntities($sql, $fileIds); |
||
| 164 | } |
||
| 165 | |||
| 166 | public function countByArtist(int $artistId) : int { |
||
| 167 | $sql = 'SELECT COUNT(*) AS `count` FROM `*PREFIX*music_tracks` WHERE `artist_id` = ?'; |
||
| 168 | $result = $this->execute($sql, [$artistId]); |
||
| 169 | $row = $result->fetch(); |
||
| 170 | return (int)$row['count']; |
||
| 171 | } |
||
| 172 | |||
| 173 | public function countByAlbum(int $albumId) : int { |
||
| 174 | $sql = 'SELECT COUNT(*) AS `count` FROM `*PREFIX*music_tracks` WHERE `album_id` = ?'; |
||
| 175 | $result = $this->execute($sql, [$albumId]); |
||
| 176 | $row = $result->fetch(); |
||
| 177 | return (int)$row['count']; |
||
| 178 | } |
||
| 179 | |||
| 180 | /** |
||
| 181 | * @return integer Duration in seconds |
||
| 182 | */ |
||
| 183 | public function totalDurationOfAlbum(int $albumId) : int { |
||
| 184 | $sql = 'SELECT SUM(`length`) AS `duration` FROM `*PREFIX*music_tracks` WHERE `album_id` = ?'; |
||
| 185 | $result = $this->execute($sql, [$albumId]); |
||
| 186 | $row = $result->fetch(); |
||
| 187 | return (int)$row['duration']; |
||
| 188 | } |
||
| 189 | |||
| 190 | /** |
||
| 191 | * @return integer Duration in seconds |
||
| 192 | */ |
||
| 193 | public function totalDurationByArtist(int $artistId) : int { |
||
| 194 | $sql = 'SELECT SUM(`length`) AS `duration` FROM `*PREFIX*music_tracks` WHERE `artist_id` = ?'; |
||
| 195 | $result = $this->execute($sql, [$artistId]); |
||
| 196 | $row = $result->fetch(); |
||
| 197 | return (int)$row['duration']; |
||
| 198 | } |
||
| 199 | |||
| 200 | /** |
||
| 201 | * Get durations of the given tracks. |
||
| 202 | * @param integer[] $trackIds |
||
| 203 | * @return array {int => int} where keys are track IDs and values are corresponding durations |
||
| 204 | */ |
||
| 205 | public function getDurations(array $trackIds) : array { |
||
| 206 | $result = []; |
||
| 207 | |||
| 208 | if (!empty($trackIds)) { |
||
| 209 | $sql = 'SELECT `id`, `length` FROM `*PREFIX*music_tracks` WHERE `id` IN ' . |
||
| 210 | $this->questionMarks(\count($trackIds)); |
||
| 211 | $rows = $this->execute($sql, $trackIds)->fetchAll(); |
||
| 212 | foreach ($rows as $row) { |
||
| 213 | $result[$row['id']] = (int)$row['length']; |
||
| 214 | } |
||
| 215 | } |
||
| 216 | return $result; |
||
| 217 | } |
||
| 218 | |||
| 219 | /** |
||
| 220 | * @return Track[] |
||
| 221 | */ |
||
| 222 | public function findAllByNameRecursive(string $name, string $userId, ?int $limit=null, ?int $offset=null) { |
||
| 223 | $condition = '( LOWER(`artist`.`name`) LIKE LOWER(?) OR |
||
| 224 | LOWER(`album`.`name`) LIKE LOWER(?) OR |
||
| 225 | LOWER(`title`) LIKE LOWER(?) )'; |
||
| 226 | $sql = $this->selectUserEntities($condition, 'ORDER BY LOWER(`title`)'); |
||
| 227 | $name = BaseMapper::prepareSubstringSearchPattern($name); |
||
| 228 | $params = [$userId, $name, $name, $name]; |
||
| 229 | return $this->findEntities($sql, $params, $limit, $offset); |
||
| 230 | } |
||
| 231 | |||
| 232 | /** |
||
| 233 | * Returns all tracks specified by name, artist name, and/or album name |
||
| 234 | * @param string|null $name the name of the track |
||
| 235 | * @param string|null $artistName the name of the artist |
||
| 236 | * @param string $userId the name of the user |
||
| 237 | * @return Track[] Tracks matching the criteria |
||
| 238 | */ |
||
| 239 | public function findAllByNameArtistOrAlbum(?string $name, ?string $artistName, ?string $albumName, string $userId) : array { |
||
| 240 | $sqlConditions = []; |
||
| 241 | $params = [$userId]; |
||
| 242 | |||
| 243 | if (!empty($name)) { |
||
| 244 | $sqlConditions[] = 'LOWER(`title`) = LOWER(?)'; |
||
| 245 | $params[] = $name; |
||
| 246 | } |
||
| 247 | |||
| 248 | if (!empty($artistName)) { |
||
| 249 | $sqlConditions[] = 'LOWER(`artist`.`name`) = LOWER(?)'; |
||
| 250 | $params[] = $artistName; |
||
| 251 | } |
||
| 252 | |||
| 253 | if (!empty($albumName)) { |
||
| 254 | $sqlConditions[] = 'LOWER(`album`.`name`) = LOWER(?)'; |
||
| 255 | $params[] = $albumName; |
||
| 256 | } |
||
| 257 | |||
| 258 | // at least one condition has to be given, otherwise return an empty set |
||
| 259 | if (\count($sqlConditions) > 0) { |
||
| 260 | $sql = $this->selectUserEntities(\implode(' AND ', $sqlConditions)); |
||
| 261 | return $this->findEntities($sql, $params); |
||
| 262 | } else { |
||
| 263 | return []; |
||
| 264 | } |
||
| 265 | } |
||
| 266 | |||
| 267 | const FAVORITE_TRACK = 0x1; |
||
| 268 | const FAVORITE_ALBUM = 0x2; |
||
| 269 | const FAVORITE_ARTIST = 0x4; |
||
| 270 | |||
| 271 | /** |
||
| 272 | * Returns all tracks specified by various criteria, all of which are optional |
||
| 273 | * @param int[] $genres Array of genre IDs |
||
| 274 | * @param int[] $artists Array of artist IDs |
||
| 275 | * @param int|null $fromYear Earliest release year to include |
||
| 276 | * @param int|null $toYear Latest release year to include |
||
| 277 | * @param int|null $favorite Bit mask of FAVORITE_TRACK, FAVORITE_ALBUM, FAVORITE_ARTIST (given favorite types are ORed in the query) |
||
| 278 | * @param int $sortBy Sorting rule as defined in the class SortBy |
||
| 279 | * @param string $userId the name of the user |
||
| 280 | * @return Track[] Tracks matching the criteria |
||
| 281 | */ |
||
| 282 | public function findAllByCriteria( |
||
| 283 | array $genres, array $artists, ?int $fromYear, ?int $toYear, ?int $favorite, |
||
| 284 | int $sortBy, bool $invertSort, string $userId, ?int $limit=null, ?int $offset=null) : array { |
||
| 285 | |||
| 286 | $sqlConditions = []; |
||
| 287 | $params = [$userId]; |
||
| 288 | |||
| 289 | if (!empty($genres)) { |
||
| 290 | $sqlConditions[] = '`genre_id` IN ' . $this->questionMarks(\count($genres)); |
||
| 291 | $params = \array_merge($params, $genres); |
||
| 292 | } |
||
| 293 | |||
| 294 | if (!empty($artists)) { |
||
| 295 | $sqlConditions[] = '`artist_id` IN ' . $this->questionMarks(\count($artists)); |
||
| 296 | $params = \array_merge($params, $artists); |
||
| 297 | } |
||
| 298 | |||
| 299 | if (!empty($fromYear)) { |
||
| 300 | $sqlConditions[] = '`year` >= ?'; |
||
| 301 | $params[] = $fromYear; |
||
| 302 | } |
||
| 303 | |||
| 304 | if (!empty($toYear)) { |
||
| 305 | $sqlConditions[] = '`year` <= ?'; |
||
| 306 | $params[] = $toYear; |
||
| 307 | } |
||
| 308 | |||
| 309 | if (!empty($favorite)) { |
||
| 310 | $favConds = []; |
||
| 311 | if ($favorite & self::FAVORITE_TRACK) { |
||
| 312 | $favConds[] = '`*PREFIX*music_tracks`.`starred` IS NOT NULL'; |
||
| 313 | } |
||
| 314 | if ($favorite & self::FAVORITE_ALBUM) { |
||
| 315 | $favConds[] = '`album`.`starred` IS NOT NULL'; |
||
| 316 | } |
||
| 317 | if ($favorite & self::FAVORITE_ARTIST) { |
||
| 318 | $favConds[] = '`artist`.`starred` IS NOT NULL'; |
||
| 319 | } |
||
| 320 | $sqlConditions[] = '(' . \implode(' OR ', $favConds) . ')'; |
||
| 321 | } |
||
| 322 | |||
| 323 | $sql = $this->selectUserEntities(\implode(' AND ', $sqlConditions), $this->formatSortingClause($sortBy, $invertSort)); |
||
| 324 | return $this->findEntities($sql, $params, $limit, $offset); |
||
| 325 | } |
||
| 326 | |||
| 327 | /** |
||
| 328 | * Find most frequently played tracks |
||
| 329 | * @return Track[] |
||
| 330 | */ |
||
| 331 | public function findFrequentPlay(string $userId, ?int $limit=null, ?int $offset=null) : array { |
||
| 332 | $sql = $this->selectUserEntities('`play_count` > 0', 'ORDER BY `play_count` DESC, LOWER(`title`)'); |
||
| 333 | return $this->findEntities($sql, [$userId], $limit, $offset); |
||
| 334 | } |
||
| 335 | |||
| 336 | /** |
||
| 337 | * Find most recently played tracks |
||
| 338 | * @return Track[] |
||
| 339 | */ |
||
| 340 | public function findRecentPlay(string $userId, ?int $limit=null, ?int $offset=null) : array { |
||
| 341 | $sql = $this->selectUserEntities('`last_played` IS NOT NULL', 'ORDER BY `last_played` DESC'); |
||
| 342 | return $this->findEntities($sql, [$userId], $limit, $offset); |
||
| 343 | } |
||
| 344 | |||
| 345 | /** |
||
| 346 | * Find least recently played tracks |
||
| 347 | * @return Track[] |
||
| 348 | */ |
||
| 349 | public function findNotRecentPlay(string $userId, ?int $limit=null, ?int $offset=null) : array { |
||
| 350 | $sql = $this->selectUserEntities(null, 'ORDER BY `last_played` ASC'); |
||
| 351 | return $this->findEntities($sql, [$userId], $limit, $offset); |
||
| 352 | } |
||
| 353 | |||
| 354 | /** |
||
| 355 | * Finds all track IDs of the user along with the parent folder ID of each track |
||
| 356 | * @return array where keys are folder IDs and values are arrays of track IDs |
||
| 357 | */ |
||
| 358 | public function findTrackAndFolderIds(string $userId) : array { |
||
| 359 | $sql = 'SELECT `track`.`id` AS id, `file`.`name` AS `filename`, `file`.`parent` AS parent |
||
| 360 | FROM `*PREFIX*music_tracks` `track` |
||
| 361 | JOIN `*PREFIX*filecache` `file` |
||
| 362 | ON `track`.`file_id` = `file`.`fileid` |
||
| 363 | WHERE `track`.`user_id` = ?'; |
||
| 364 | |||
| 365 | $rows = $this->execute($sql, [$userId])->fetchAll(); |
||
| 366 | |||
| 367 | // Sort the results according the file names. This can't be made using ORDERBY in the |
||
| 368 | // SQL query because then we couldn't use the "natural order" comparison algorithm |
||
| 369 | \usort($rows, function ($a, $b) { |
||
| 370 | return \strnatcasecmp($a['filename'], $b['filename']); |
||
| 371 | }); |
||
| 372 | |||
| 373 | // group the files to parent folder "buckets" |
||
| 374 | $result = []; |
||
| 375 | foreach ($rows as $row) { |
||
| 376 | $result[(int)$row['parent']][] = (int)$row['id']; |
||
| 377 | } |
||
| 378 | |||
| 379 | return $result; |
||
| 380 | } |
||
| 381 | |||
| 382 | /** |
||
| 383 | * Find names and parents of the file system nodes with given IDs within the given storage |
||
| 384 | * @param int[] $nodeIds |
||
| 385 | * @param string $storageId |
||
| 386 | * @return array where keys are the node IDs and values are associative arrays |
||
| 387 | * like { 'name' => string, 'parent' => int }; |
||
| 388 | */ |
||
| 389 | public function findNodeNamesAndParents(array $nodeIds, string $storageId) : array { |
||
| 390 | $result = []; |
||
| 391 | |||
| 392 | if (!empty($nodeIds)) { |
||
| 393 | $sql = 'SELECT `fileid`, `name`, `parent` '. |
||
| 394 | 'FROM `*PREFIX*filecache` `filecache` '. |
||
| 395 | 'JOIN `*PREFIX*storages` `storages` '. |
||
| 396 | 'ON `filecache`.`storage` = `storages`.`numeric_id` '. |
||
| 397 | 'WHERE `storages`.`id` = ? '. |
||
| 398 | 'AND `filecache`.`fileid` IN '. $this->questionMarks(\count($nodeIds)); |
||
| 399 | |||
| 400 | $rows = $this->execute($sql, \array_merge([$storageId], $nodeIds))->fetchAll(); |
||
| 401 | |||
| 402 | foreach ($rows as $row) { |
||
| 403 | $result[$row['fileid']] = [ |
||
| 404 | 'name' => $row['name'], |
||
| 405 | 'parent' => (int)$row['parent'] |
||
| 406 | ]; |
||
| 407 | } |
||
| 408 | } |
||
| 409 | |||
| 410 | return $result; |
||
| 411 | } |
||
| 412 | |||
| 413 | /** |
||
| 414 | * Returns all genre IDs associated with the given artist |
||
| 415 | * @return int[] |
||
| 416 | */ |
||
| 417 | public function getGenresByArtistId(int $artistId, string $userId) : array { |
||
| 418 | $sql = 'SELECT DISTINCT(`genre_id`) FROM `*PREFIX*music_tracks` WHERE |
||
| 419 | `genre_id` IS NOT NULL AND `user_id` = ? AND `artist_id` = ?'; |
||
| 420 | $rows = $this->execute($sql, [$userId, $artistId]); |
||
| 421 | return $rows->fetchAll(\PDO::FETCH_COLUMN); |
||
| 422 | } |
||
| 423 | |||
| 424 | /** |
||
| 425 | * Returns all tracks IDs of the user, organized by the genre_id. |
||
| 426 | * @return array where keys are genre IDs and values are arrays of track IDs |
||
| 427 | */ |
||
| 428 | public function mapGenreIdsToTrackIds(string $userId) : array { |
||
| 429 | $sql = 'SELECT `id`, `genre_id` FROM `*PREFIX*music_tracks` |
||
| 430 | WHERE `genre_id` IS NOT NULL and `user_id` = ?'; |
||
| 431 | $rows = $this->execute($sql, [$userId])->fetchAll(); |
||
| 432 | |||
| 433 | $result = []; |
||
| 434 | foreach ($rows as $row) { |
||
| 435 | $result[(int)$row['genre_id']][] = (int)$row['id']; |
||
| 436 | } |
||
| 437 | |||
| 438 | return $result; |
||
| 439 | } |
||
| 440 | |||
| 441 | /** |
||
| 442 | * Returns file IDs of the tracks which do not have genre scanned. This is not the same |
||
| 443 | * thing as unknown genre, which means that the genre has been scanned but was not found |
||
| 444 | * from the track metadata. |
||
| 445 | * @return int[] |
||
| 446 | */ |
||
| 447 | public function findFilesWithoutScannedGenre(string $userId) : array { |
||
| 448 | $sql = 'SELECT `track`.`file_id` FROM `*PREFIX*music_tracks` `track` |
||
| 449 | INNER JOIN `*PREFIX*filecache` `file` |
||
| 450 | ON `track`.`file_id` = `file`.`fileid` |
||
| 451 | WHERE `genre_id` IS NULL and `user_id` = ?'; |
||
| 452 | $rows = $this->execute($sql, [$userId]); |
||
| 453 | return $rows->fetchAll(\PDO::FETCH_COLUMN); |
||
| 454 | } |
||
| 455 | |||
| 456 | /** |
||
| 457 | * Update "last played" timestamp and increment the total play count of the track. |
||
| 458 | * The DB row is updated *without* updating the `updated` column. |
||
| 459 | * @return bool true if the track was found and updated, false otherwise |
||
| 460 | */ |
||
| 461 | public function recordTrackPlayed(int $trackId, string $userId, \DateTime $timeOfPlay) : bool { |
||
| 462 | $sql = 'UPDATE `*PREFIX*music_tracks` |
||
| 463 | SET `last_played` = ?, `play_count` = `play_count` + 1 |
||
| 464 | WHERE `user_id` = ? AND `id` = ?'; |
||
| 465 | $params = [$timeOfPlay->format(BaseMapper::SQL_DATE_FORMAT), $userId, $trackId]; |
||
| 466 | $result = $this->execute($sql, $params); |
||
| 467 | return ($result->rowCount() > 0); |
||
| 468 | } |
||
| 469 | |||
| 470 | /** |
||
| 471 | * Marks tracks as dirty, ultimately requesting the user to rescan them |
||
| 472 | * @param int[] $fileIds file IDs of the tracks to mark as dirty |
||
| 473 | * @param string[]|null $userIds the target users; if omitted, the tracks matching the |
||
| 474 | * $fileIds are marked for all users |
||
| 475 | * @return int number of rows affected |
||
| 476 | */ |
||
| 477 | public function markTracksDirty(array $fileIds, ?array $userIds=null) : int { |
||
| 478 | $sql = 'UPDATE `*PREFIX*music_tracks` |
||
| 479 | SET `dirty` = 1 |
||
| 480 | WHERE `file_id` IN ' . $this->questionMarks(\count($fileIds)); |
||
| 481 | $params = $fileIds; |
||
| 482 | |||
| 483 | if (!empty($userIds)) { |
||
| 484 | $sql .= ' AND `user_id` IN ' . $this->questionMarks(\count($userIds)); |
||
| 485 | $params = \array_merge($params, $userIds); |
||
| 486 | } |
||
| 487 | |||
| 488 | $result = $this->execute($sql, $params); |
||
| 489 | return $result->rowCount(); |
||
| 490 | } |
||
| 491 | |||
| 492 | /** |
||
| 493 | * Overridden from the base implementation to provide support for table-specific rules |
||
| 494 | * |
||
| 495 | * {@inheritdoc} |
||
| 496 | * @see BaseMapper::advFormatSqlCondition() |
||
| 497 | */ |
||
| 498 | protected function advFormatSqlCondition(string $rule, string $sqlOp, string $conv) : string { |
||
| 499 | // The extra subquery "mysqlhack" seen around some nested queries is needed in order for these to not be insanely slow on MySQL. |
||
| 500 | switch ($rule) { |
||
| 501 | case 'anywhere': return self::formatAdvSearchAnywhereCond($sqlOp, $conv); |
||
| 502 | case 'album': return "$conv(`album`.`name`) $sqlOp $conv(?)"; |
||
| 503 | case 'artist': return "$conv(`artist`.`name`) $sqlOp $conv(?)"; |
||
| 504 | case 'album_artist': return "`album_id` IN (SELECT `al`.`id` from `*PREFIX*music_albums` `al` JOIN `*PREFIX*music_artists` `ar` ON `al`.`album_artist_id` = `ar`.`id` WHERE $conv(`ar`.`name`) $sqlOp $conv(?))"; |
||
| 505 | case 'album_artist_id': return "$sqlOp `album_id` IN (SELECT `id` from `*PREFIX*music_albums` WHERE `album_artist_id` = ?)"; // our own API extension |
||
| 506 | case 'track': return "`number` $sqlOp ?"; |
||
| 507 | case 'year': return "`year` $sqlOp ?"; |
||
| 508 | case 'albumrating': return "`album`.`rating` $sqlOp ?"; |
||
| 509 | case 'artistrating': return "`artist`.`rating` $sqlOp ?"; |
||
| 510 | case 'favorite_album': return "$conv(`album`.`name`) $sqlOp $conv(?) AND `album`.`starred` IS NOT NULL"; |
||
| 511 | case 'favorite_artist': return "$conv(`artist`.`name`) $sqlOp $conv(?) AND `artist`.`starred` IS NOT NULL"; |
||
| 512 | case 'played_times': return "`play_count` $sqlOp ?"; |
||
| 513 | case 'last_play': return "`last_played` $sqlOp ?"; |
||
| 514 | case 'played': // fall through, we give no access to other people's data |
||
| 515 | case 'myplayed': return "`last_played` $sqlOp"; // operator "IS NULL" or "IS NOT NULL" |
||
| 516 | case 'myplayedalbum': return "`album_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" |
||
| 517 | case 'myplayedartist': return "`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" |
||
| 518 | case 'time': return "`length` $sqlOp ?"; |
||
| 519 | case 'bitrate': return "`bitrate` $sqlOp ?"; |
||
| 520 | case 'genre': // fall through |
||
| 521 | case 'song_genre': return "$conv(`genre`.`name`) $sqlOp $conv(?)"; |
||
| 522 | case 'album_genre': return "`album_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)"; |
||
| 523 | case 'artist_genre': return "`artist_id` IN (SELECT * FROM (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*music_genres` `g` ON `t`.`genre_id` = `g`.`id` GROUP BY `artist_id` HAVING $conv(" . $this->sqlGroupConcat('`g`.`name`') . ") $sqlOp $conv(?)) mysqlhack)"; |
||
| 524 | case 'no_genre': return ($sqlOp == 'IS NOT NULL') ? '`genre`.`name` = ""' : '`genre`.`name` != ""'; |
||
| 525 | case 'playlist': return "$sqlOp EXISTS (SELECT 1 from `*PREFIX*music_playlists` `p` WHERE `p`.`id` = ? AND `p`.`track_ids` LIKE " . $this->sqlConcat("'%|'", "`*PREFIX*music_tracks`.`id`", "'|%'") . ')'; |
||
| 526 | case 'playlist_name': return "EXISTS (SELECT 1 from `*PREFIX*music_playlists` `p` WHERE $conv(`p`.`name`) $sqlOp $conv(?) AND `p`.`track_ids` LIKE " . $this->sqlConcat("'%|'", "`*PREFIX*music_tracks`.`id`", "'|%'") . ')'; |
||
| 527 | case 'recent_played': return "`*PREFIX*music_tracks`.`id` IN (SELECT * FROM (SELECT `id` FROM `*PREFIX*music_tracks` WHERE `user_id` = ? ORDER BY `last_played` DESC LIMIT $sqlOp) mysqlhack)"; |
||
| 528 | case 'file': return "$conv(`file`.`name`) $sqlOp $conv(?)"; |
||
| 529 | case 'mbid_song': return parent::advFormatSqlCondition('mbid', $sqlOp, $conv); // alias |
||
| 530 | case 'mbid_album': return "`album`.`mbid` $sqlOp ?"; |
||
| 531 | case 'mbid_artist': return "`artist`.`mbid` $sqlOp ?"; |
||
| 532 | default: return parent::advFormatSqlCondition($rule, $sqlOp, $conv); |
||
| 533 | } |
||
| 534 | } |
||
| 535 | |||
| 536 | private static function formatAdvSearchAnywhereCond(string $sqlOp, string $conv) : string { |
||
| 552 | } |
||
| 553 | |||
| 554 | /** |
||
| 555 | * {@inheritdoc} |
||
| 556 | * @see \OCA\Music\Db\BaseMapper::findUniqueEntity() |
||
| 557 | * @param Track $track |
||
| 558 | * @return Track |
||
| 559 | */ |
||
| 560 | protected function findUniqueEntity(Entity $track) : Entity { |
||
| 561 | return $this->findByFileId($track->getFileId(), $track->getUserId()); |
||
| 562 | } |
||
| 563 | } |
||
| 564 |