1use crate::db::queries::{
10 AlbumFilter, AlbumOrder, AlbumQuery, ArtistOrder, ArtistQuery, LOCAL_USER, PlayedSince,
11 TrackFilter, TrackOrder,
12};
13use crate::shelves::{self, Shelf, Tracks};
14
15#[derive(Debug, Clone, Default, PartialEq, Eq)]
18pub struct BrowseFilter {
19 pub favourites: bool,
20 pub recent: bool,
23 pub downloaded: bool,
25 pub lossless: bool,
27 pub codec: Option<String>,
28 pub genre: Option<String>,
29 pub year_from: Option<i32>,
32 pub year_to: Option<i32>,
33}
34
35impl BrowseFilter {
36 pub fn active_count(&self) -> usize {
38 [
39 self.favourites,
40 self.recent,
41 self.downloaded,
42 self.lossless,
43 self.codec.is_some(),
44 self.genre.is_some(),
45 self.year_from.is_some() || self.year_to.is_some(),
46 ]
47 .into_iter()
48 .filter(|on| *on)
49 .count()
50 }
51
52 fn played(&self) -> Option<PlayedSince> {
53 self.recent
54 .then(|| Shelf::Recent.albums(LOCAL_USER, shelves::now()).played)
55 .flatten()
56 }
57
58 fn albums(&self) -> AlbumFilter<'_> {
59 AlbumFilter {
60 lossless: self.lossless,
61 codec: trimmed(self.codec.as_deref()),
62 year_from: self.year_from,
63 year_to: self.year_to,
64 genre: trimmed(self.genre.as_deref()),
65 on_device: self.downloaded || crate::remote::offline::active(),
66 }
67 }
68}
69
70#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
72pub enum AlbumSort {
73 #[default]
75 RecentlyAdded,
76 Title,
77 Artist,
78 Year,
79 Random,
82 LastPlayed,
84 Downloaded,
86 BestMatch,
88}
89
90impl AlbumSort {
91 pub fn offered(filter: &BrowseFilter, searching: bool) -> Vec<AlbumSort> {
94 let mut sorts = Vec::new();
95 if searching {
96 sorts.push(AlbumSort::BestMatch);
97 }
98 if filter.recent {
99 sorts.push(AlbumSort::LastPlayed);
100 }
101 if filter.downloaded {
102 sorts.push(AlbumSort::Downloaded);
103 }
104 sorts.extend([
105 AlbumSort::RecentlyAdded,
106 AlbumSort::Title,
107 AlbumSort::Artist,
108 AlbumSort::Year,
109 AlbumSort::Random,
110 ]);
111 sorts
112 }
113
114 fn order(self, seed: i64) -> AlbumOrder {
115 match self {
116 AlbumSort::RecentlyAdded => AlbumOrder::RecentlyAdded,
117 AlbumSort::Title => AlbumOrder::Title,
118 AlbumSort::Artist => AlbumOrder::ArtistThenDate,
119 AlbumSort::Year => AlbumOrder::YearDesc,
120 AlbumSort::Random => AlbumOrder::Random(seed),
121 AlbumSort::LastPlayed => AlbumOrder::LastPlayed,
122 AlbumSort::Downloaded => AlbumOrder::Downloaded,
123 AlbumSort::BestMatch => AlbumOrder::Relevance,
124 }
125 }
126}
127
128#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
130pub enum TrackSort {
131 #[default]
133 Artist,
134 Title,
135 Album,
137 Duration,
138 LastPlayed,
140 BestMatch,
142}
143
144impl TrackSort {
145 pub fn offered(filter: &BrowseFilter, searching: bool) -> Vec<TrackSort> {
147 let mut sorts = Vec::new();
148 if searching {
149 sorts.push(TrackSort::BestMatch);
150 }
151 if filter.recent {
152 sorts.push(TrackSort::LastPlayed);
153 }
154 sorts.extend([
155 TrackSort::Artist,
156 TrackSort::Title,
157 TrackSort::Album,
158 TrackSort::Duration,
159 ]);
160 sorts
161 }
162
163 fn order(self) -> (TrackOrder, bool) {
165 match self {
166 TrackSort::Artist => (TrackOrder::ArtistAlbumDiscTrack, false),
167 TrackSort::Title => (TrackOrder::Title, false),
168 TrackSort::Album => (TrackOrder::Album, false),
169 TrackSort::Duration => (TrackOrder::Duration, false),
170 TrackSort::LastPlayed => (TrackOrder::LastPlayed, true),
171 TrackSort::BestMatch => (TrackOrder::Relevance, false),
172 }
173 }
174}
175
176pub fn trimmed(search: Option<&str>) -> Option<&str> {
178 search.map(str::trim).filter(|s| !s.is_empty())
179}
180
181pub fn albums<'a>(
184 filter: &'a BrowseFilter,
185 search: Option<&'a str>,
186 sort: AlbumSort,
187 seed: i64,
188) -> AlbumQuery<'a> {
189 AlbumQuery {
190 search: trimmed(search),
191 order: sort.order(seed),
192 favourites_of: filter.favourites.then_some(LOCAL_USER),
193 played: filter.played(),
194 filter: filter.albums(),
195 ..Default::default()
196 }
197}
198
199pub fn artists<'a>(filter: &'a BrowseFilter, search: Option<&'a str>) -> ArtistQuery<'a> {
203 let played = filter.played();
204 ArtistQuery {
205 search: trimmed(search),
206 favourites_of: filter.favourites.then_some(LOCAL_USER),
207 order: if played.is_some() {
208 ArtistOrder::LastPlayed
209 } else {
210 ArtistOrder::Relevance
211 },
212 played,
213 filter: filter.albums(),
214 ..Default::default()
215 }
216}
217
218pub fn tracks(filter: &BrowseFilter, search: Option<&str>, sort: TrackSort) -> Tracks {
220 let (order, descending) = sort.order();
221 let (user, now) = (LOCAL_USER, shelves::now());
222 Tracks {
223 filter: TrackFilter {
224 search: trimmed(search).and_then(|q| Shelf::Search(q).tracks(user, now).filter.search),
225 favourites_of: filter.favourites.then_some(user),
226 played: filter.played(),
227 codec: trimmed(filter.codec.as_deref()).map(str::to_owned),
228 genre: trimmed(filter.genre.as_deref()).map(str::to_owned),
229 year_start: filter.year_from,
230 year_end: filter.year_to,
231 on_device: filter.downloaded || crate::remote::offline::active(),
232 ..Default::default()
233 },
234 order,
235 descending,
236 }
237}
238
239pub fn of_shelf(shelf: Shelf) -> (BrowseFilter, AlbumSort, TrackSort) {
243 match shelf {
244 Shelf::Favourites => (
245 BrowseFilter {
246 favourites: true,
247 ..Default::default()
248 },
249 AlbumSort::Artist,
250 TrackSort::Artist,
251 ),
252 Shelf::Recent => (
253 BrowseFilter {
254 recent: true,
255 ..Default::default()
256 },
257 AlbumSort::LastPlayed,
258 TrackSort::LastPlayed,
259 ),
260 Shelf::Search(_) => (
261 BrowseFilter::default(),
262 AlbumSort::BestMatch,
263 TrackSort::BestMatch,
264 ),
265 Shelf::Downloaded => (
266 BrowseFilter {
267 downloaded: true,
268 ..Default::default()
269 },
270 AlbumSort::Downloaded,
271 TrackSort::Artist,
272 ),
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 #[test]
281 fn sorts_are_offered_where_they_mean_something() {
282 let none = BrowseFilter::default();
283 assert_eq!(
284 AlbumSort::offered(&none, false)[0],
285 AlbumSort::RecentlyAdded
286 );
287 assert!(!AlbumSort::offered(&none, false).contains(&AlbumSort::LastPlayed));
288 let recent = BrowseFilter {
289 recent: true,
290 downloaded: true,
291 ..Default::default()
292 };
293 assert_eq!(
294 AlbumSort::offered(&recent, true)[..3],
295 [
296 AlbumSort::BestMatch,
297 AlbumSort::LastPlayed,
298 AlbumSort::Downloaded
299 ]
300 );
301 assert_eq!(TrackSort::offered(&recent, false)[0], TrackSort::LastPlayed);
302 }
303
304 #[test]
305 fn a_year_range_counts_once() {
306 let f = BrowseFilter {
307 year_from: Some(1990),
308 year_to: Some(1999),
309 lossless: true,
310 ..Default::default()
311 };
312 assert_eq!(f.active_count(), 2);
313 assert_eq!(BrowseFilter::default().active_count(), 0);
314 }
315
316 #[test]
317 fn a_blank_name_filter_is_no_filter() {
318 let f = BrowseFilter::default();
319 assert_eq!(albums(&f, Some(" "), AlbumSort::Title, 0).search, None);
320 assert_eq!(artists(&f, Some(" x ")).search, Some("x"));
321 }
322}