Skip to content

Commit

Permalink
Add new smart list filter "Favorite"
Browse files Browse the repository at this point in the history
This new filter allows finding favorite tracks, tracks on favorite albums,
tracks by favorite artists, or the combination of all of the previous.
  • Loading branch information
paulijar committed May 29, 2024
1 parent 230d4c6 commit ab8f860
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Additional tabs "Albums" and "Tracks" to the artist details pane
- Additional tabs "Tracks" and "Artists" to the album details pane
- Favorite toggle to the details pane of the tracks, albums, artists, playlists, and podcasts
- New filter "Favorite" for the smart list

### Changed
- Drop support for PHP versions older 7.4 (i.e. PHP 7.1 - 7.3)
Expand Down
1 change: 1 addition & 0 deletions js/app/controllers/sidebar/smartlistfilterscontroller.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ angular.module('Music').controller('SmartListFiltersController', [
$('#filter-genres').chosen();
$('#filter-artists').chosen();
$('#filter-history').chosen({allow_single_deselect: true, disable_search: true, placeholder_text_single: ' '});
$('#filter-favorite').chosen({allow_single_deselect: true, disable_search: true, placeholder_text_single: ' '});
const $chosenInputs = $('#smartlist-filters .chosen-container');
const $filterGenres = $('#filter-genres');
const $filterSize = $('#filter-size');
Expand Down
17 changes: 15 additions & 2 deletions lib/BusinessLayer/PlaylistBusinessLayer.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,13 @@ public function getDuration(int $playlistId, string $userId) : int {
* @param int[] $artists Array of artist IDs
* @param int|null $fromYear Earliest release year to include
* @param int|null $toYear Latest release year to include
* @param string|null $favorite One of: 'track', 'album', 'artists', 'track_album_artist', null
* @param int $size Size of the playlist to generate, provided that there are enough matching tracks
* @param string $userId the name of the user
*/
public function generate(
?string $history, bool $historyStrict, array $genres, array $artists,
?int $fromYear, ?int $toYear, int $size, string $userId) : Playlist {
?int $fromYear, ?int $toYear, ?string $favorite, int $size, string $userId) : Playlist {

$now = new \DateTime();
$nowStr = $now->format(PlaylistMapper::SQL_DATE_FORMAT);
Expand All @@ -212,7 +213,9 @@ public function generate(
list('sortBy' => $sortBy, 'invert' => $invertSort) = self::sortRulesForHistory($history);
$limit = ($sortBy === SortBy::None) ? null : ($historyStrict ? $size : $size * 4);

$tracks = $this->trackMapper->findAllByCriteria($genres, $artists, $fromYear, $toYear, $sortBy, $invertSort, $userId, $limit);
$favoriteMask = self::favoriteMask($favorite);

$tracks = $this->trackMapper->findAllByCriteria($genres, $artists, $fromYear, $toYear, $favoriteMask, $sortBy, $invertSort, $userId, $limit);

if ($sortBy !== SortBy::None && !$historyStrict) {
// When generating by non-strict history, use a pool of tracks at maximum twice the size of final list.
Expand Down Expand Up @@ -247,4 +250,14 @@ private static function sortRulesForHistory(?string $history) : array {
return ['sortBy' => SortBy::None, 'invert' => false];
}
}

private static function favoriteMask(?string $mode) : ?int {
switch ($mode) {
case 'track': return TrackMapper::FAVORITE_TRACK;
case 'album': return TrackMapper::FAVORITE_ALBUM;
case 'artist': return TrackMapper::FAVORITE_ARTIST;
case 'track_album_artist': return TrackMapper::FAVORITE_TRACK | TrackMapper::FAVORITE_ALBUM | TrackMapper::FAVORITE_ARTIST;
default: return null;
}
}
}
7 changes: 5 additions & 2 deletions lib/Controller/PlaylistApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,15 @@ private function toFullTree($playlist) {
*/
public function generate(
?bool $useLatestParams, ?string $history, ?string $genres, ?string $artists,
?int $fromYear, ?int $toYear, int $size=100, string $historyStrict='false') {
?int $fromYear, ?int $toYear, ?string $favorite=null, int $size=100, string $historyStrict='false') {

if ($useLatestParams) {
$history = $this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_history') ?: null;
$genres = $this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_genres') ?: null;
$artists = $this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_artists') ?: null;
$fromYear = (int)$this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_from_year') ?: null;
$toYear = (int)$this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_to_year') ?: null;
$favorite = $this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_favorite') ?: null;
$size = (int)$this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_size', 100);
$historyStrict = $this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_history_strict', 'false');
} else {
Expand All @@ -181,6 +182,7 @@ public function generate(
$this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_artists', $artists ?? '');
$this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_from_year', (string)$fromYear);
$this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_to_year', (string)$toYear);
$this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_favorite', $favorite ?? '');
$this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_size', (string)$size);
$this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_history_strict', $historyStrict);
}
Expand All @@ -191,7 +193,7 @@ public function generate(
$artists = $this->artistBusinessLayer->findAllIds($this->userId, self::toIntArray($artists));

$playlist = $this->playlistBusinessLayer->generate(
$history, $historyStrict, $genres, $artists, $fromYear, $toYear, $size, $this->userId);
$history, $historyStrict, $genres, $artists, $fromYear, $toYear, $favorite, $size, $this->userId);
$result = $playlist->toAPI($this->urlGenerator);

$result['params'] = [
Expand All @@ -201,6 +203,7 @@ public function generate(
'artists' => \implode(',', $artists) ?: null,
'fromYear' => $fromYear ?: null,
'toYear' => $toYear ?: null,
'favorite' => $favorite ?: null,
'size' => $size
];

Expand Down
21 changes: 20 additions & 1 deletion lib/Db/TrackMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -268,18 +268,23 @@ public function findAllByNameArtistOrAlbum(?string $name, ?string $artistName, ?
}
}

const FAVORITE_TRACK = 0x1;
const FAVORITE_ALBUM = 0x2;
const FAVORITE_ARTIST = 0x4;

/**
* Returns all tracks specified by various criteria, all of which are optional
* @param int[] $genres Array of genre IDs
* @param int[] $artists Array of artist IDs
* @param int|null $fromYear Earliest release year to include
* @param int|null $toYear Latest release year to include
* @param int|null $favorite Bit mask of FAVORITE_TRACK, FAVORITE_ALBUM, FAVORITE_ARTIST (given favorite types are ORed in the query)
* @param int $sortBy Sorting rule as defined in the class SortBy
* @param string $userId the name of the user
* @return Track[] Tracks matching the criteria
*/
public function findAllByCriteria(
array $genres, array $artists, ?int $fromYear, ?int $toYear,
array $genres, array $artists, ?int $fromYear, ?int $toYear, ?int $favorite,
int $sortBy, bool $invertSort, string $userId, ?int $limit=null, ?int $offset=null) : array {

$sqlConditions = [];
Expand All @@ -305,6 +310,20 @@ public function findAllByCriteria(
$params[] = $toYear;
}

if (!empty($favorite)) {
$favConds = [];
if ($favorite & self::FAVORITE_TRACK) {
$favConds[] = '`*PREFIX*music_tracks`.`starred` IS NOT NULL';
}
if ($favorite & self::FAVORITE_ALBUM) {
$favConds[] = '`album`.`starred` IS NOT NULL';
}
if ($favorite & self::FAVORITE_ARTIST) {
$favConds[] = '`artist`.`starred` IS NOT NULL';
}
$sqlConditions[] = '(' . \implode(' OR ', $favConds) . ')';
}

$sql = $this->selectUserEntities(\implode(' AND ', $sqlConditions), $this->formatSortingClause($sortBy, $invertSort));
return $this->findEntities($sql, $params, $limit, $offset);
}
Expand Down
11 changes: 11 additions & 0 deletions templates/partials/sidebar/smartlistfilters.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,16 @@
<input id="filter-history-strict" type="checkbox" ng-model="smartListParams.historyStrict" />
</div>
<div>
<label for="filter-favorite" translate>Favorite</label>
<select id="filter-favorite" ng-model="smartListParams.favorite">
<option value=""></option>
<option value="track" translate>Favorite track</option>
<option value="album" translate>Favorite album</option>
<option value="artist" translate>Favorite artist</option>
<option value="track_album_artist" translate>Favorite track, album, or artist</option>
</select>
</div>
<div><button id="update-button" ng-click="onUpdateButton()" ng-disabled="!fieldsValid" translate>Update</button></div>
</div>

0 comments on commit ab8f860

Please sign in to comment.