1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
use std::collections::{BTreeMap, HashMap};
use crate::database::models::{playlists::playlist::Playlist, tracks::Track};
#[derive(Debug, Clone, Default)]
pub struct StorageContext {
/// Global playlist for tracks. Tracks that belong to any amount of playlist(s) are included here.
all_tracks: Vec<Track>,
/// Filtered version of [`Self::all_tracks`] based on a search input.
filtered_all_tracks: Option<Vec<Track>>,
/// All playlists and the respective tracks loaded from the database at startup.
/// Using a BTreeMap since this is used directly in the UI and we don't want the possibility of
/// the playlists list looking different each startup.
playlist_tracks: BTreeMap<Playlist, Vec<Track>>,
/// Filtered tracks based on a search.
/// Hashmap is used due to the playlists not being displayed directly in the UI, while [`Self::playlist_tracks`] is.
/// Probably will switch to BTreeMap in the future once searching for playlists is implemented.
filtered_playlist_tracks: HashMap<Playlist, Vec<Track>>,
}
impl StorageContext {
/// Gets tracks from a playlist in [`Self::playlist_tracks`],
/// if none is selected then [`Self::all_tracks`] is returned.
pub fn get_playlist_tracks(&self, playlist: Option<&Playlist>) -> Option<&Vec<Track>> {
if let Some(playlist) = playlist {
self.playlist_tracks.get(playlist)
} else {
Some(&self.all_tracks)
}
}
/// Get an iterator of all playlist keys from the unfiltered [`Self::playlist_tracks`] playlist tracks.
pub fn playlists(&self) -> impl Iterator<Item = &Playlist> {
self.playlist_tracks.keys()
}
/// Sets a playlist's tracks to the passed track vector after sorting it based on comparing track paths.
/// If no playlist is passed, then all tracks are set to the sorted track vector.
pub fn set_playlist_tracks(&mut self, playlist: Option<Playlist>, mut tracks: Vec<Track>) {
tracks.sort_by(|a, b| a.path.cmp(&b.path));
if let Some(playlist) = playlist {
self.playlist_tracks.insert(playlist, tracks);
} else {
self.all_tracks = tracks;
}
}
/// Sorted in-place insertion of a track to [`Self::all_tracks`], comparing by path on insert.
fn insert_sorted_to_all_tracks(&mut self, track: Track) {
let pos = self
.all_tracks
.binary_search_by(|other_track| other_track.path.cmp(&track.path))
.unwrap_or_else(|e| e);
self.all_tracks.insert(pos, track);
}
/// Sorted in-place insertion of a track to a playlist's vector
/// in [`Self::playlist_tracks`], comparing by path on insert.
fn insert_sorted_to_playlist(playlist_tracks: &mut Vec<Track>, track: Track) {
let pos = playlist_tracks
.binary_search_by(|other_track| other_track.path.cmp(&track.path))
.unwrap_or_else(|e| e);
playlist_tracks.insert(pos, track);
}
/// Extends the track list in a playlist of [`Self::playlist_tracks`] with the passed through tracks.
///
/// If the playlist is None, then all tracks is inserted to.
/// When the playlist is some but doesn't exist in the [`Self::playlist_tracks`] attribute,
/// a new vector is created and tracks are added to it.
pub fn add_tracks_to_playlist(&mut self, playlist: Option<&Playlist>, tracks: Vec<Track>) {
for track in tracks {
if let Some(playlist) = playlist {
let playlist_tracks = self.playlist_tracks.entry(playlist.clone()).or_default();
Self::insert_sorted_to_playlist(playlist_tracks, track.clone());
}
self.insert_sorted_to_all_tracks(track);
}
}
/// Create a playlist in [`Self::playlist_tracks`] with an empty vector of tracks.
pub fn add_empty_playlist(&mut self, playlist: &Playlist) {
self.playlist_tracks.insert(playlist.clone(), Vec::new());
}
/// Returns a playlist's tracks, checking filtered track attributes first, eventually narrowing down to the global tracks playlist [`Self::all_tracks`].
///
/// If playlist is [`Some`], [`Self::filtered_playlist_tracks`] is checked first to see if it contains the playlist.
/// If so, the tracks that key referred to are returned. Otherwise [`Self::playlist_tracks`] is checked. If that still doesn't contain
/// the playlist then it is assumed no filtering was applied and no tracks exist for the playlist, and an empty slice is returned.
/// If the playlist is [`None`], [`Self::filtered_all_tracks`] is checked to see if it is [`Some`], otherwise return [`Self::all_tracks`].
pub fn filtered_tracks(&self, playlist: Option<&Playlist>) -> &[Track] {
if let Some(playlist) = playlist {
if let Some(filtered) = self.filtered_playlist_tracks.get(playlist) {
filtered.as_slice()
} else if let Some(playlist_tracks) = self.playlist_tracks.get(playlist) {
playlist_tracks.as_slice()
} else {
&[]
}
} else if let Some(filtered_all_tracks) = &self.filtered_all_tracks {
filtered_all_tracks.as_slice()
} else {
self.all_tracks.as_slice()
}
}
/// Apply a filter function on a playlist's tracks and create a new track vector from it.
///
/// If the playlist is [`Some`], the predicate filters the playlist in [`Self::playlist_tracks`] to see what is valid in it.
/// Once the filtered vector is created, the playlist is set to the new vector in [`Self::filtered_playlist_tracks`].
/// If playlist is [`None`], the same filtering logic is instead applied to [`Self::all_tracks`],
/// and any filtered tracks is set on [`Self::filtered_all_tracks`].
///
/// An example of a predicate filtering track names down to ones that only contain "foo":
/// ```
/// use daemos::{context::StorageContext, database::models::tracks::Track};
///
/// let mut storage = StorageContext::default();
/// let tracks = vec![
/// Track { name: "foo 1".to_string(), ..Default::default() },
/// Track { name: "bar 2".to_string(), ..Default::default() },
/// ];
/// storage.set_playlist_tracks(None, tracks);
///
/// let result = storage.filter_with(&None, |track| track.name.contains("foo"));
/// assert_eq!(result.unwrap().len(), 1);
/// assert_eq!(result.unwrap()[0].name, "foo 1");
/// ```
pub fn filter_with<F>(
&mut self,
playlist: &Option<Playlist>,
predicate: F,
) -> Option<&Vec<Track>>
where
F: Fn(&Track) -> bool,
{
if let Some(playlist) = playlist {
let source = self.playlist_tracks.get(playlist)?;
let filtered = source.iter().filter(|&x| predicate(x)).cloned().collect();
self.filtered_playlist_tracks
.insert(playlist.clone(), filtered);
self.filtered_playlist_tracks.get(playlist)
} else {
self.filtered_all_tracks = Some(
self.all_tracks
.iter()
.filter(|&x| predicate(x))
.cloned()
.collect(),
);
self.filtered_all_tracks.as_ref()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filter_with_global_tracks() {
let mut storage = StorageContext::default();
let tracks = vec![
Track {
name: "foo 1".to_string(),
..Default::default()
},
Track {
name: "bar 2".to_string(),
..Default::default()
},
];
storage.set_playlist_tracks(None, tracks);
let result = storage.filter_with(&None, |track| track.name.contains("foo"));
assert_eq!(result.unwrap().len(), 1);
assert_eq!(result.unwrap()[0].name, "foo 1");
}
#[test]
fn test_filter_with_playlist_tracks() {
let mut storage = StorageContext::default();
let tracks = vec![
Track {
name: "song 1".to_string(),
..Default::default()
},
Track {
name: "song 2".to_string(),
..Default::default()
},
];
let playlist = Playlist {
name: "test".to_string(),
..Default::default()
};
storage.set_playlist_tracks(Some(playlist.clone()), tracks);
let result = storage.filter_with(&Some(playlist), |track| track.name.contains("song 1"));
assert_eq!(result.unwrap().len(), 1);
assert_eq!(result.unwrap()[0].name, "song 1");
}
}