Skip to main content

chilen_backend/music_lib/state/library/
mod.rs

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    /// Returns the default playlist name ("New Playlist").
490    ///
491    /// If a playlist with the default name exists, then a number will be added to the end of the
492    /// playlist name so it's unique, eg. "New Playlist 1", "New Playlist 2", etc.
493    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    /// Retrieves a track corresponding to the provided hash.
504    pub fn find_track_by_hash(&self, hash: u64) -> Option<&Arc<Track>> {
505        self.tracks_by_hash.get(&hash)
506    }
507
508    /// Retrieves tracks corresponding to a list of provided hashes.
509    pub fn find_tracks_by_hashes(&self, hashes: &[u64]) -> HashMatchingResult {
510        // In most cases all tracks will match
511        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    // TODO: Partial importing when there are missing tracks (also should be reflected in the error
697    // type) and complete failure when the playlist is unreadable
698    // TEST: Check if importing M3U8 files works correctly
699    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    /// Adds a new favorite item to the library.
723    ///
724    /// Returns whether the item was already marked as a favorite.
725    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    /// Removes an item from the favorites list.
732    ///
733    /// Returns whether the item was marked as a favorite.
734    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}