use crate::db::queries::{
AlbumFilter, AlbumOrder, AlbumQuery, ArtistOrder, ArtistQuery, LOCAL_USER, PlayedSince,
TrackFilter, TrackOrder,
};
use crate::shelves::{self, Shelf, Tracks};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct BrowseFilter {
pub favourites: bool,
pub recent: bool,
pub downloaded: bool,
pub lossless: bool,
pub codec: Option<String>,
pub genre: Option<String>,
pub year_from: Option<i32>,
pub year_to: Option<i32>,
}
impl BrowseFilter {
pub fn active_count(&self) -> usize {
[
self.favourites,
self.recent,
self.downloaded,
self.lossless,
self.codec.is_some(),
self.genre.is_some(),
self.year_from.is_some() || self.year_to.is_some(),
]
.into_iter()
.filter(|on| *on)
.count()
}
fn played(&self) -> Option<PlayedSince> {
self.recent
.then(|| Shelf::Recent.albums(LOCAL_USER, shelves::now()).played)
.flatten()
}
fn albums(&self) -> AlbumFilter<'_> {
AlbumFilter {
lossless: self.lossless,
codec: trimmed(self.codec.as_deref()),
year_from: self.year_from,
year_to: self.year_to,
genre: trimmed(self.genre.as_deref()),
on_device: self.downloaded || crate::remote::offline::active(),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum AlbumSort {
#[default]
RecentlyAdded,
Title,
Artist,
Year,
Random,
LastPlayed,
Downloaded,
BestMatch,
}
impl AlbumSort {
pub fn offered(filter: &BrowseFilter, searching: bool) -> Vec<AlbumSort> {
let mut sorts = Vec::new();
if searching {
sorts.push(AlbumSort::BestMatch);
}
if filter.recent {
sorts.push(AlbumSort::LastPlayed);
}
if filter.downloaded {
sorts.push(AlbumSort::Downloaded);
}
sorts.extend([
AlbumSort::RecentlyAdded,
AlbumSort::Title,
AlbumSort::Artist,
AlbumSort::Year,
AlbumSort::Random,
]);
sorts
}
fn order(self, seed: i64) -> AlbumOrder {
match self {
AlbumSort::RecentlyAdded => AlbumOrder::RecentlyAdded,
AlbumSort::Title => AlbumOrder::Title,
AlbumSort::Artist => AlbumOrder::ArtistThenDate,
AlbumSort::Year => AlbumOrder::YearDesc,
AlbumSort::Random => AlbumOrder::Random(seed),
AlbumSort::LastPlayed => AlbumOrder::LastPlayed,
AlbumSort::Downloaded => AlbumOrder::Downloaded,
AlbumSort::BestMatch => AlbumOrder::Relevance,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum TrackSort {
#[default]
Artist,
Title,
Album,
Duration,
LastPlayed,
BestMatch,
}
impl TrackSort {
pub fn offered(filter: &BrowseFilter, searching: bool) -> Vec<TrackSort> {
let mut sorts = Vec::new();
if searching {
sorts.push(TrackSort::BestMatch);
}
if filter.recent {
sorts.push(TrackSort::LastPlayed);
}
sorts.extend([
TrackSort::Artist,
TrackSort::Title,
TrackSort::Album,
TrackSort::Duration,
]);
sorts
}
fn order(self) -> (TrackOrder, bool) {
match self {
TrackSort::Artist => (TrackOrder::ArtistAlbumDiscTrack, false),
TrackSort::Title => (TrackOrder::Title, false),
TrackSort::Album => (TrackOrder::Album, false),
TrackSort::Duration => (TrackOrder::Duration, false),
TrackSort::LastPlayed => (TrackOrder::LastPlayed, true),
TrackSort::BestMatch => (TrackOrder::Relevance, false),
}
}
}
pub fn trimmed(search: Option<&str>) -> Option<&str> {
search.map(str::trim).filter(|s| !s.is_empty())
}
pub fn albums<'a>(
filter: &'a BrowseFilter,
search: Option<&'a str>,
sort: AlbumSort,
seed: i64,
) -> AlbumQuery<'a> {
AlbumQuery {
search: trimmed(search),
order: sort.order(seed),
favourites_of: filter.favourites.then_some(LOCAL_USER),
played: filter.played(),
filter: filter.albums(),
..Default::default()
}
}
pub fn artists<'a>(filter: &'a BrowseFilter, search: Option<&'a str>) -> ArtistQuery<'a> {
let played = filter.played();
ArtistQuery {
search: trimmed(search),
favourites_of: filter.favourites.then_some(LOCAL_USER),
order: if played.is_some() {
ArtistOrder::LastPlayed
} else {
ArtistOrder::Relevance
},
played,
filter: filter.albums(),
..Default::default()
}
}
pub fn tracks(filter: &BrowseFilter, search: Option<&str>, sort: TrackSort) -> Tracks {
let (order, descending) = sort.order();
let (user, now) = (LOCAL_USER, shelves::now());
Tracks {
filter: TrackFilter {
search: trimmed(search).and_then(|q| Shelf::Search(q).tracks(user, now).filter.search),
favourites_of: filter.favourites.then_some(user),
played: filter.played(),
codec: trimmed(filter.codec.as_deref()).map(str::to_owned),
genre: trimmed(filter.genre.as_deref()).map(str::to_owned),
year_start: filter.year_from,
year_end: filter.year_to,
on_device: filter.downloaded || crate::remote::offline::active(),
..Default::default()
},
order,
descending,
}
}
pub fn of_shelf(shelf: Shelf) -> (BrowseFilter, AlbumSort, TrackSort) {
match shelf {
Shelf::Favourites => (
BrowseFilter {
favourites: true,
..Default::default()
},
AlbumSort::Artist,
TrackSort::Artist,
),
Shelf::Recent => (
BrowseFilter {
recent: true,
..Default::default()
},
AlbumSort::LastPlayed,
TrackSort::LastPlayed,
),
Shelf::Search(_) => (
BrowseFilter::default(),
AlbumSort::BestMatch,
TrackSort::BestMatch,
),
Shelf::Downloaded => (
BrowseFilter {
downloaded: true,
..Default::default()
},
AlbumSort::Downloaded,
TrackSort::Artist,
),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sorts_are_offered_where_they_mean_something() {
let none = BrowseFilter::default();
assert_eq!(
AlbumSort::offered(&none, false)[0],
AlbumSort::RecentlyAdded
);
assert!(!AlbumSort::offered(&none, false).contains(&AlbumSort::LastPlayed));
let recent = BrowseFilter {
recent: true,
downloaded: true,
..Default::default()
};
assert_eq!(
AlbumSort::offered(&recent, true)[..3],
[
AlbumSort::BestMatch,
AlbumSort::LastPlayed,
AlbumSort::Downloaded
]
);
assert_eq!(TrackSort::offered(&recent, false)[0], TrackSort::LastPlayed);
}
#[test]
fn a_year_range_counts_once() {
let f = BrowseFilter {
year_from: Some(1990),
year_to: Some(1999),
lossless: true,
..Default::default()
};
assert_eq!(f.active_count(), 2);
assert_eq!(BrowseFilter::default().active_count(), 0);
}
#[test]
fn a_blank_name_filter_is_no_filter() {
let f = BrowseFilter::default();
assert_eq!(albums(&f, Some(" "), AlbumSort::Title, 0).search, None);
assert_eq!(artists(&f, Some(" x ")).search, Some("x"));
}
}