chilen_backend/music_lib/state/
playlist.rs1use std::{hash::Hash, sync::Arc, time::Duration};
2
3use log::warn;
4use serde::{Deserialize, Serialize};
5
6#[cfg(test)]
7use crate::music_lib::indexer::CacheMode;
8use crate::{
9 Error,
10 music_lib::{
11 indexer::{
12 self,
13 covers::{Cover, get_playlist_cover},
14 },
15 state::{MusicLibrary, Track},
16 },
17};
18
19#[derive(Clone, Debug, PartialEq, Eq, Hash)]
20pub struct Playlist {
21 pub name: Box<str>,
22 pub tracks: Box<[Arc<Track>]>,
23 pub duration: Duration,
24 pub cover: Cover,
25 pub id: u64,
30 pub(crate) unmatched: Box<[u64]>,
31}
32
33impl Playlist {
34 pub(super) fn load(
35 lib: &mut MusicLibrary,
36 loaded: PlaylistStored,
37 config: indexer::Config,
38 ) -> Self {
39 let result = lib.find_tracks_by_hashes(&loaded.track_hashes);
40 if !result.unmatched.is_empty() {
41 warn!(
42 "{} missing tracks in playlist {}",
43 result.unmatched.len(),
44 loaded.name
45 );
46 }
47 let duration = result.matched.iter().map(|t| t.duration).sum();
48
49 #[cfg(not(test))]
50 let cover =
51 get_playlist_cover(&loaded.name, config, &result.matched).unwrap_or(Cover::none());
52 #[cfg(test)]
53 let cover = if config.cache_mode != CacheMode::Disabled {
54 get_playlist_cover(&loaded.name, config, &result.matched).unwrap_or(Cover::none())
55 } else {
56 Cover::none()
57 };
58
59 Self {
60 name: loaded.name.into(),
61 tracks: result.matched.into(),
62 duration,
63 unmatched: result.unmatched.into(),
64 id: lib.get_playlist_id(),
65 cover,
66 }
67 }
68
69 pub(crate) fn remove_tracks(&mut self, mut ids: Vec<usize>) -> Result<(), Error> {
70 ids.sort_unstable();
71
72 if ids.windows(2).any(|pair| pair[0] == pair[1]) {
73 return Err(Error::DuplicateItems);
74 }
75 if ids.iter().any(|&i| i >= self.tracks.len()) {
76 return Err(Error::IndexOutOfBounds);
77 }
78
79 let mut ids = ids.into_iter().peekable();
80 let mut tracks = std::mem::take(&mut self.tracks).into_vec();
81 let mut index = 0;
82
83 tracks.retain(|_| {
84 let remove = ids.peek() == Some(&index);
85 if remove {
86 ids.next();
87 }
88 index += 1;
89 !remove
90 });
91
92 self.tracks = tracks.into_boxed_slice();
93
94 Ok(())
95 }
96}
97
98#[derive(Clone, Debug, Serialize, Deserialize)]
99pub(crate) struct PlaylistStored {
100 name: String,
101 track_hashes: Vec<u64>,
102}
103
104impl From<Playlist> for PlaylistStored {
105 fn from(value: Playlist) -> Self {
106 let mut track_hashes: Vec<_> = value.tracks.iter().map(|t| t.hash_self()).collect();
107 track_hashes.extend(value.unmatched);
108 Self {
109 name: value.name.to_string(),
110 track_hashes,
111 }
112 }
113}