1#[cfg(test)]
2mod tests;
3
4use std::{
5 collections::{HashMap, HashSet},
6 hash::Hash,
7 path::PathBuf,
8 sync::Arc,
9 time::Duration,
10};
11
12pub use lofty::tag::items::Timestamp;
13use log::{error, trace};
14use serde::{Deserialize, Serialize};
15
16#[cfg(test)]
17use crate::music_lib::indexer::CacheMode;
18use crate::{
19 COLLATOR, Error, Event, get_config,
20 music_lib::{
21 indexer::{
22 self,
23 covers::{Cover, get_playlist_cover},
24 },
25 sorting::{
26 sort_albums, sort_albums_chronologically, sort_artists, sort_genres, sort_playlists,
27 sort_tracks, sort_tracks_chronologically, sort_tracks_in_album,
28 },
29 state::{Playlist, PlaylistStored, Progress, Track},
30 tracks_from_m3u8,
31 },
32};
33
34const DEFAULT_PLAYLIST_NAME: &str = "New Playlist";
35
36#[derive(Clone, Debug, PartialEq)]
37pub struct HashMatchingResult {
38 pub matched: Vec<Arc<Track>>,
39 pub unmatched: Vec<u64>,
40}
41
42#[derive(Clone, Debug, PartialEq, Eq, Hash)]
43pub struct Album {
44 pub title: Box<str>,
45 pub cover: Cover,
46 pub artists: Box<[Box<str>]>,
47 pub tracks: Box<[Arc<Track>]>,
48 pub date: Option<Timestamp>,
49 pub duration: Duration,
50}
51
52#[derive(Clone, Debug, PartialEq, Eq, Hash)]
53pub struct Artist {
54 pub name: Box<str>,
55 pub cover: Cover,
56 pub tracks: Box<[Arc<Track>]>,
57 pub albums: Box<[Arc<Album>]>,
58}
59
60#[derive(Clone, Debug, PartialEq, Eq, Hash)]
61pub struct Genre {
62 pub name: Box<str>,
63 pub cover: Cover,
64 pub artists: Box<[Arc<Artist>]>,
65 pub albums: Box<[Arc<Album>]>,
66 pub tracks: Box<[Arc<Track>]>,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Hash)]
70pub enum LibraryItem {
71 Track(Arc<Track>),
72 Playlist(Arc<Playlist>),
73 Album(Arc<Album>),
74 Artist(Arc<Artist>),
75 Genre(Arc<Genre>),
76}
77
78impl From<Arc<Track>> for LibraryItem {
79 fn from(value: Arc<Track>) -> Self {
80 Self::Track(value)
81 }
82}
83
84impl From<Arc<Playlist>> for LibraryItem {
85 fn from(value: Arc<Playlist>) -> Self {
86 Self::Playlist(value)
87 }
88}
89
90impl From<Arc<Album>> for LibraryItem {
91 fn from(value: Arc<Album>) -> Self {
92 Self::Album(value)
93 }
94}
95
96impl From<Arc<Artist>> for LibraryItem {
97 fn from(value: Arc<Artist>) -> Self {
98 Self::Artist(value)
99 }
100}
101
102impl From<Arc<Genre>> for LibraryItem {
103 fn from(value: Arc<Genre>) -> Self {
104 Self::Genre(value)
105 }
106}
107
108#[derive(Clone, Debug, PartialEq)]
109pub struct MusicLibrary {
110 pub playlists: Vec<Arc<Playlist>>,
111 pub favorites: HashSet<LibraryItem>,
112 pub tracks: Vec<Arc<Track>>,
113 pub albums: Vec<Arc<Album>>,
114 pub artists: Vec<Arc<Artist>>,
115 pub genres: Vec<Arc<Genre>>,
116 pub total_duration: Duration,
117 playlist_id_counter: u64,
118 tracks_by_path: HashMap<Box<str>, Arc<Track>>,
119 tracks_by_hash: HashMap<u64, Arc<Track>>,
120 playlists_by_name: HashMap<Box<str>, Arc<Playlist>>,
121 playlists_by_id: HashMap<u64, Arc<Playlist>>,
122 artists_by_name: HashMap<Box<str>, Arc<Artist>>,
123 albums_by_title: HashMap<Box<str>, Arc<Album>>,
124 genres_by_name: HashMap<Box<str>, Arc<Genre>>,
125}
126
127impl MusicLibrary {
128 pub(super) fn new(tracks: Vec<Track>) -> Self {
129 crate::send_event(Event::LoadProgressChanged(Progress::RebuildingLibrary));
130
131 let mut tracks: Vec<_> = tracks.into_iter().map(Arc::new).collect();
132
133 let collator = COLLATOR.read().unwrap().clone();
134 let collator = collator.as_ref();
135
136 sort_tracks(&mut tracks);
137
138 let album_titles: HashSet<_> = tracks.iter().flat_map(|t| &t.album).collect();
139 let artist_names: HashSet<_> = tracks
140 .iter()
141 .flat_map(|t| t.artists.as_ref().into_iter().flatten())
142 .collect();
143 let genre_names: HashSet<_> = tracks
144 .iter()
145 .flat_map(|t| t.genres.as_ref().into_iter().flatten())
146 .collect();
147
148 let mut tracks_by_artist: HashMap<&Box<str>, HashSet<Arc<Track>>> =
149 HashMap::with_capacity(artist_names.len());
150 let mut tracks_by_album: HashMap<&Box<str>, HashSet<Arc<Track>>> =
151 HashMap::with_capacity(album_titles.len());
152 let mut tracks_by_genre: HashMap<&Box<str>, HashSet<Arc<Track>>> =
153 HashMap::with_capacity(genre_names.len());
154
155 for track in &tracks {
156 if let Some(artists) = &track.artists {
157 for artist in artists {
158 if let Some(val) = tracks_by_artist.get_mut(artist) {
159 val.insert(track.clone());
160 } else {
161 tracks_by_artist.insert(artist, [track.clone()].into_iter().collect());
162 }
163 }
164 }
165 if let Some(album) = &track.album {
166 if let Some(val) = tracks_by_album.get_mut(album) {
167 val.insert(track.clone());
168 } else {
169 tracks_by_album.insert(album, [track.clone()].into_iter().collect());
170 }
171 }
172 if let Some(genres) = &track.genres {
173 for genre in genres {
174 if let Some(val) = tracks_by_genre.get_mut(genre) {
175 val.insert(track.clone());
176 } else {
177 tracks_by_genre.insert(genre, [track.clone()].into_iter().collect());
178 }
179 }
180 }
181 }
182
183 let mut albums: Vec<_> = album_titles
184 .into_iter()
185 .map(|title| {
186 let mut tracks: Vec<_> = tracks_by_album[title].clone().into_iter().collect();
187 sort_tracks_in_album(&mut tracks);
188
189 let mut artists: Vec<_> = tracks
190 .iter()
191 .flat_map(|t| t.artists.clone().into_iter().flatten())
192 .collect::<HashSet<Box<str>>>()
193 .into_iter()
194 .collect();
195 match &collator {
196 Some(collator) => {
197 artists.sort_by(|a1, a2| collator.compare(a1, a2));
198 }
199 None => {
200 artists.sort();
201 }
202 }
203
204 let mut counts: HashMap<Cover, usize> = HashMap::with_capacity(tracks.len());
205 for track in &tracks {
206 *counts.entry(track.cover.clone()).or_insert(0) += 1;
207 }
208 let commonest = counts.into_iter().max_by_key(|(_, c)| *c).map(|(k, _)| k);
209 let cover = commonest.unwrap_or(tracks[0].cover.clone());
210
211 let date = tracks.iter().filter_map(|t| t.date).max();
212 let duration = tracks.iter().map(|t| t.duration).sum();
213
214 Arc::new(Album {
215 title: title.to_string().into(),
216 cover,
217 tracks: tracks.into(),
218 artists: artists.into(),
219 date,
220 duration,
221 })
222 })
223 .collect();
224 sort_albums(&mut albums);
225
226 let albums_by_title: HashMap<Box<str>, Arc<Album>> = albums
227 .iter()
228 .map(|a| (a.title.clone(), a.clone()))
229 .collect();
230
231 let mut albums_by_artist: HashMap<&Box<str>, HashSet<Arc<Album>>> =
232 HashMap::with_capacity(artist_names.len());
233 for album in &albums {
234 for artist in &album.artists {
235 if let Some(val) = albums_by_artist.get_mut(&artist) {
236 val.insert(album.clone());
237 } else {
238 albums_by_artist.insert(artist, [album.clone()].into_iter().collect());
239 }
240 }
241 }
242
243 let mut artists: Vec<_> = artist_names
244 .into_iter()
245 .map(|name| {
246 let mut tracks: Vec<_> = tracks_by_artist[name].clone().into_iter().collect();
247 sort_tracks_chronologically(&mut tracks);
248
249 let mut albums = if let Some(albums) = albums_by_artist.get(name) {
250 albums.iter().cloned().collect()
251 } else {
252 Vec::new()
253 };
254 sort_albums_chronologically(&mut albums);
255
256 let cover = tracks
257 .first()
258 .map(|t| t.cover.clone())
259 .unwrap_or(Cover::none());
260
261 Arc::new(Artist {
262 name: name.to_string().into(),
263 cover,
264 tracks: tracks.into(),
265 albums: albums.into(),
266 })
267 })
268 .collect();
269 sort_artists(&mut artists);
270
271 let mut artists_by_album: HashMap<&Box<str>, HashSet<Arc<Artist>>> =
272 HashMap::with_capacity(albums.len());
273 for artist in &artists {
274 for album in &artist.albums {
275 if let Some(val) = artists_by_album.get_mut(&album.title) {
276 val.insert(artist.clone());
277 } else {
278 artists_by_album.insert(&album.title, [artist.clone()].into_iter().collect());
279 }
280 }
281 }
282
283 let mut albums_by_genre: HashMap<&Box<str>, HashSet<Arc<Album>>> =
284 HashMap::with_capacity(albums.len());
285 for album in &albums {
286 for track in &album.tracks {
287 if let Some(genres) = &track.genres {
288 for genre in genres {
289 if let Some(val) = albums_by_genre.get_mut(genre) {
290 val.insert(album.clone());
291 } else {
292 albums_by_genre.insert(genre, [album.clone()].into_iter().collect());
293 }
294 }
295 }
296 }
297 }
298
299 let mut artists_by_genre: HashMap<&Box<str>, HashSet<Arc<Artist>>> =
300 HashMap::with_capacity(artists.len());
301 for artist in &artists {
302 for track in &artist.tracks {
303 if let Some(genres) = &track.genres {
304 for genre in genres {
305 if let Some(val) = artists_by_genre.get_mut(genre) {
306 val.insert(artist.clone());
307 } else {
308 artists_by_genre.insert(genre, [artist.clone()].into_iter().collect());
309 }
310 }
311 }
312 }
313 }
314
315 let mut genres: Vec<_> = genre_names
316 .into_iter()
317 .map(|name| {
318 let mut tracks: Vec<_> = tracks_by_genre[name].clone().into_iter().collect();
319 sort_tracks(&mut tracks);
320
321 let mut counts: HashMap<Cover, usize> = HashMap::with_capacity(tracks.len());
322 for track in &tracks {
323 *counts.entry(track.cover.clone()).or_insert(0) += 1;
324 }
325 let commonest = counts.into_iter().max_by_key(|(_, c)| *c).map(|(k, _)| k);
326 let cover = commonest.unwrap_or(tracks[0].cover.clone());
327
328 let mut artists: Vec<_> = artists_by_genre
329 .get(name)
330 .cloned()
331 .into_iter()
332 .flatten()
333 .collect();
334 sort_artists(&mut artists);
335
336 let mut albums: Vec<_> = albums_by_genre
337 .get(name)
338 .cloned()
339 .into_iter()
340 .flatten()
341 .collect();
342 sort_albums(&mut albums);
343
344 Arc::new(Genre {
345 name: name.to_string().into(),
346 cover,
347 tracks: tracks.into(),
348 albums: albums.into(),
349 artists: artists.into(),
350 })
351 })
352 .collect();
353
354 let genres_by_name: HashMap<Box<str>, Arc<Genre>> =
355 genres.iter().map(|g| (g.name.clone(), g.clone())).collect();
356
357 sort_genres(&mut genres);
358
359 let mut track_path_map: HashMap<Box<str>, _> = HashMap::with_capacity(tracks.len());
360 for t in tracks.iter() {
361 track_path_map.insert(t.path.to_string_lossy().to_string().into(), t.clone());
362 }
363
364 let mut track_hash_map: HashMap<_, _> = HashMap::with_capacity(tracks.len());
365 for t in tracks.iter() {
366 track_hash_map.insert(t.hash_self(), t.clone());
367 }
368
369 let artists_by_name: HashMap<Box<str>, Arc<Artist>> = artists
370 .iter()
371 .map(|a| (a.name.clone(), a.clone()))
372 .collect();
373
374 let total_duration = tracks.iter().map(|t| t.duration).sum();
375
376 Self {
377 playlists: Vec::new(),
378 favorites: HashSet::new(),
379 tracks,
380 albums,
381 artists,
382 genres,
383 playlist_id_counter: 0,
384 tracks_by_path: track_path_map,
385 tracks_by_hash: track_hash_map,
386 playlists_by_name: HashMap::new(),
387 playlists_by_id: HashMap::new(),
388 artists_by_name,
389 albums_by_title,
390 genres_by_name,
391 total_duration,
392 }
393 }
394
395 pub(super) fn load(
396 loaded: MusicLibraryStored,
397 tracks: Vec<Track>,
398 config: indexer::Config,
399 ) -> Self {
400 crate::send_event(Event::LoadProgressChanged(Progress::RestoringState));
401
402 let mut lib = Self::new(tracks);
403
404 let mut playlists = Vec::with_capacity(loaded.playlists.len());
405 lib.playlists_by_name = HashMap::with_capacity(loaded.playlists.len());
406 lib.playlists_by_id = HashMap::with_capacity(loaded.playlists.len());
407 for p in loaded.playlists {
408 let playlist = Arc::new(Playlist::load(&mut lib, p, config));
409 playlists.push(playlist.clone());
410 lib.playlists_by_name
411 .insert(playlist.name.clone(), playlist.clone());
412 lib.playlists_by_id.insert(playlist.id, playlist);
413 }
414
415 let favorites = loaded
416 .favorites
417 .into_iter()
418 .flat_map(|favorite| match favorite {
419 LibraryItemStored::Track(hash) => {
420 lib.find_track_by_hash(hash).cloned().map(|t| t.into())
421 }
422 LibraryItemStored::Playlist(name) => {
423 lib.find_playlist_by_name(&name).cloned().map(|p| p.into())
424 }
425 LibraryItemStored::Album(title) => {
426 lib.find_album(&title).cloned().map(|a| a.into())
427 }
428 LibraryItemStored::Artist(name) => {
429 lib.find_artist(&name).cloned().map(|a| a.into())
430 }
431 LibraryItemStored::Genre(name) => lib.find_genre(&name).cloned().map(|g| g.into()),
432 })
433 .collect();
434
435 lib.favorites = favorites;
436 lib.playlists = playlists;
437
438 sort_playlists(&mut lib.playlists);
439
440 lib
441 }
442
443 #[cfg(test)]
444 pub(crate) fn new_testing(tracks: Vec<Track>) -> Self {
445 Self::new(tracks)
446 }
447
448 pub(super) fn get_playlist_id(&mut self) -> u64 {
449 self.playlist_id_counter += 1;
450 self.playlist_id_counter
451 }
452
453 fn check_name(&self, name: &str) -> Result<(), Error> {
454 let name = name.trim();
455 if name.is_empty() {
456 return Err(Error::EmptyName);
457 }
458 if self.find_playlist_by_name(name).is_some() {
459 error!("A playlist with name \"{name}\" already exists");
460 return Err(Error::PlaylistExists);
461 }
462 Ok(())
463 }
464
465 pub fn find_track_by_path(&self, path: &str) -> Option<Arc<Track>> {
466 self.tracks_by_path.get(path).cloned()
467 }
468
469 pub fn find_artist(&self, name: &str) -> Option<&Arc<Artist>> {
470 self.artists_by_name.get(name)
471 }
472
473 pub fn find_playlist_by_name(&self, name: &str) -> Option<&Arc<Playlist>> {
474 self.playlists_by_name.get(name)
475 }
476
477 pub fn find_playlist_by_id(&self, id: u64) -> Option<&Arc<Playlist>> {
478 self.playlists_by_id.get(&id)
479 }
480
481 pub fn find_album(&self, title: &str) -> Option<&Arc<Album>> {
482 self.albums_by_title.get(title)
483 }
484
485 pub fn find_genre(&self, name: &str) -> Option<&Arc<Genre>> {
486 self.genres_by_name.get(name)
487 }
488
489 pub fn get_default_playlist_name(&self) -> String {
494 let mut i = 0;
495 let mut playlist_name = DEFAULT_PLAYLIST_NAME.to_string();
496 while self.find_playlist_by_name(&playlist_name).is_some() {
497 i += 1;
498 playlist_name = format!("{DEFAULT_PLAYLIST_NAME} {i}");
499 }
500 playlist_name
501 }
502
503 pub fn find_track_by_hash(&self, hash: u64) -> Option<&Arc<Track>> {
505 self.tracks_by_hash.get(&hash)
506 }
507
508 pub fn find_tracks_by_hashes(&self, hashes: &[u64]) -> HashMatchingResult {
510 let mut tracks = Vec::with_capacity(hashes.len());
512 let mut unmatched = Vec::new();
513 for hash in hashes {
514 if let Some(track) = self.tracks_by_hash.get(hash) {
515 tracks.push(track.clone());
516 } else {
517 unmatched.push(*hash);
518 }
519 }
520 tracks.shrink_to_fit();
521 HashMatchingResult {
522 matched: tracks,
523 unmatched,
524 }
525 }
526
527 pub fn remove_playlists(&mut self, mut playlists: Vec<&str>) -> Result<(), Error> {
528 playlists.sort();
529 let mut unique = playlists.clone();
530 unique.dedup();
531 if unique != playlists {
532 return Err(Error::DuplicateItems);
533 }
534
535 for name in &playlists {
536 if let Some(index) = self.playlists.iter().position(|p| p.name.as_ref() == *name) {
537 let id = self.playlists[index].id;
538 self.playlists.remove(index);
539 self.playlists_by_name.remove(*name);
540 self.playlists_by_id.remove(&id);
541 } else {
542 return Err(Error::UnknownPlaylist(name.to_string()));
543 }
544 }
545
546 crate::send_event(Event::LibraryChanged(Box::new(self.clone())));
547 Ok(())
548 }
549
550 pub fn create_playlist(
551 &mut self,
552 name: &str,
553 track_paths: &Option<Vec<PathBuf>>,
554 ) -> Result<(), Error> {
555 trace!("Creating a new playlist \"{name}\" from a list of tracks");
556
557 let name = name.trim();
558 self.check_name(name)?;
559
560 let tracks = if let Some(tracks) = track_paths {
561 let mut out = Vec::with_capacity(tracks.len());
562 for path in tracks {
563 if let Some(track) = self.find_track_by_path(&path.to_string_lossy()) {
564 out.push(track);
565 } else {
566 error!("The track {path:?} was not found in the music library");
567 return Err(Error::UnknownTrackPath(path.to_path_buf()));
568 }
569 }
570 out
571 } else {
572 Vec::new()
573 };
574
575 let duration = tracks.iter().map(|t| t.duration).sum();
576 let config = get_config();
577
578 #[cfg(not(test))]
579 let cover =
580 get_playlist_cover(name, config.library.indexer, &tracks).unwrap_or(Cover::none());
581 #[cfg(test)]
582 let cover = if config.library.indexer.cache_mode != CacheMode::Disabled {
583 get_playlist_cover(name, config.library.indexer, &tracks).unwrap_or(Cover::none())
584 } else {
585 Cover::none()
586 };
587
588 let playlist = Arc::new(Playlist {
589 name: name.to_string().into_boxed_str(),
590 tracks: tracks.into(),
591 duration,
592 unmatched: Box::new([]),
593 id: self.get_playlist_id(),
594 cover,
595 });
596 self.playlists.push(playlist.clone());
597 self.playlists_by_name.insert(name.into(), playlist);
598 sort_playlists(&mut self.playlists);
599 crate::send_event(Event::LibraryChanged(Box::new(self.clone())));
600 Ok(())
601 }
602
603 pub fn rename_playlist(&mut self, source: &str, target: &str) -> Result<(), Error> {
604 let source = source.trim();
605 let target = target.trim();
606 trace!("Renaming playlist \"{source}\" to \"{target}\"");
607 self.check_name(target)?;
608
609 let src_playlist_index = match self
610 .playlists
611 .iter()
612 .position(|p| p.name.as_ref() == source)
613 {
614 Some(pos) => pos,
615 None => return Err(Error::UnknownPlaylist(source.to_string())),
616 };
617 let src_playlist = self.playlists[src_playlist_index].clone();
618 let playlist_id = src_playlist.id;
619 let mut playlist = src_playlist.as_ref().clone();
620 playlist.name = target.to_string().into();
621
622 self.playlists.remove(src_playlist_index);
623 self.playlists_by_name.remove(source);
624 self.playlists_by_id.remove(&playlist_id);
625 let playlist = Arc::new(playlist);
626 self.playlists.push(playlist.clone());
627 self.playlists_by_name
628 .insert(target.into(), playlist.clone());
629 self.playlists_by_id.insert(playlist_id, playlist);
630 sort_playlists(&mut self.playlists);
631
632 crate::send_event(Event::LibraryChanged(Box::new(self.clone())));
633 Ok(())
634 }
635
636 pub fn add_tracks(&mut self, name: &str, tracks: &[u64]) -> Result<(), Error> {
637 let name = name.trim();
638 trace!("Adding tracks to playlist \"{name}\"");
639
640 let playlist_index = match self.playlists.iter().position(|p| p.name.as_ref() == name) {
641 Some(i) => i,
642 None => return Err(Error::UnknownPlaylist(name.to_string())),
643 };
644
645 let mut result = self.find_tracks_by_hashes(tracks);
646 if let Some(unmatched) = result.unmatched.first() {
647 return Err(Error::UnknownTrack { hash: *unmatched });
648 }
649
650 let mut playlist = self.playlists[playlist_index].as_ref().clone();
651 self.playlists.remove(playlist_index);
652 self.playlists_by_name.remove(&playlist.name);
653 self.playlists_by_id.remove(&playlist.id);
654
655 let mut tracks = std::mem::take(&mut playlist.tracks).into_vec();
656 tracks.append(&mut result.matched);
657 playlist.tracks = tracks.into_boxed_slice();
658
659 let playlist = Arc::new(playlist);
660 self.playlists.push(playlist.clone());
661 self.playlists_by_name
662 .insert(playlist.name.clone(), playlist.clone());
663 self.playlists_by_id.insert(playlist.id, playlist);
664 sort_playlists(&mut self.playlists);
665
666 crate::send_event(Event::LibraryChanged(Box::new(self.clone())));
667 Ok(())
668 }
669
670 pub fn remove_tracks(&mut self, name: &str, tracks: Vec<usize>) -> Result<(), Error> {
671 let name = name.trim();
672 trace!("Removing tracks from playlist \"{name}\"");
673
674 let playlist_index = match self.playlists.iter().position(|p| p.name.as_ref() == name) {
675 Some(i) => i,
676 None => return Err(Error::UnknownPlaylist(name.to_string())),
677 };
678 let mut playlist = self.playlists[playlist_index].as_ref().clone();
679
680 self.playlists.remove(playlist_index);
681 self.playlists_by_name.remove(&playlist.name);
682 self.playlists_by_id.remove(&playlist.id);
683
684 playlist.remove_tracks(tracks)?;
685 let playlist = Arc::new(playlist);
686 self.playlists.push(playlist.clone());
687 self.playlists_by_name
688 .insert(playlist.name.clone(), playlist.clone());
689 self.playlists_by_id.insert(playlist.id, playlist);
690 sort_playlists(&mut self.playlists);
691
692 crate::send_event(Event::LibraryChanged(Box::new(self.clone())));
693 Ok(())
694 }
695
696 pub fn import_m3u8_playlist(
700 &mut self,
701 path: &PathBuf,
702 name: Option<String>,
703 ) -> Result<(), Error> {
704 trace!("Importing a playlist from an M3U8 file at {path:?}");
705 let tracks = tracks_from_m3u8(path)?;
706 let name = match name {
707 Some(n) => n,
708 None => {
709 if let Some(path) = path.file_name() {
710 let path = path.to_string_lossy().to_string();
711 path.strip_suffix(".m3u8")
712 .unwrap_or(path.strip_suffix(".m3u").unwrap_or(&path))
713 .to_string()
714 } else {
715 self.get_default_playlist_name()
716 }
717 }
718 };
719 self.create_playlist(&name, &Some(tracks))
720 }
721
722 pub fn add_favorite(&mut self, favorite: LibraryItem) -> Result<(), Error> {
726 self.favorites.insert(favorite);
727 crate::send_event(Event::LibraryChanged(Box::new(self.clone())));
728 Ok(())
729 }
730
731 pub fn remove_favorite(&mut self, favorite: &LibraryItem) -> Result<(), Error> {
735 self.favorites.remove(favorite);
736 crate::send_event(Event::LibraryChanged(Box::new(self.clone())));
737 Ok(())
738 }
739}
740
741#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
742pub(super) enum LibraryItemStored {
743 Track(u64),
744 Playlist(String),
745 Album(String),
746 Artist(String),
747 Genre(String),
748}
749
750impl From<LibraryItem> for LibraryItemStored {
751 fn from(value: LibraryItem) -> Self {
752 match value {
753 LibraryItem::Track(track) => Self::Track(track.hash_self()),
754 LibraryItem::Playlist(playlist) => Self::Playlist(playlist.name.clone().into_string()),
755 LibraryItem::Album(album) => Self::Album(album.title.clone().into_string()),
756 LibraryItem::Artist(artist) => Self::Artist(artist.name.clone().into_string()),
757 LibraryItem::Genre(genre) => Self::Genre(genre.name.clone().into_string()),
758 }
759 }
760}
761
762#[derive(Clone, Debug, Serialize, Deserialize)]
763pub(super) struct MusicLibraryStored {
764 playlists: Vec<PlaylistStored>,
765 favorites: Vec<LibraryItemStored>,
766}
767
768impl From<MusicLibrary> for MusicLibraryStored {
769 fn from(value: MusicLibrary) -> Self {
770 Self {
771 playlists: value
772 .playlists
773 .into_iter()
774 .map(|t| t.as_ref().clone().into())
775 .collect(),
776 favorites: value.favorites.into_iter().map(|i| i.into()).collect(),
777 }
778 }
779}