1 use std::fmt; 2 use std::sync::{Arc, RwLock}; 3 4 use chrono::{DateTime, Utc}; 5 use rspotify::model::album::FullAlbum; 6 use rspotify::model::track::{FullTrack, SavedTrack, SimplifiedTrack}; 7 use rspotify::model::Id; 8 9 use crate::library::Library; 10 use crate::model::album::Album; 11 use crate::model::artist::Artist; 12 use crate::model::playable::Playable; 13 use crate::queue::Queue; 14 use crate::traits::{IntoBoxedViewExt, ListItem, ViewExt}; 15 use crate::ui::listview::ListView; 16 17 #[derive(Clone, Deserialize, Serialize)] 18 pub struct Track { 19 pub id: Option<String>, 20 pub uri: String, 21 pub title: String, 22 pub track_number: u32, 23 pub disc_number: i32, 24 pub duration: u32, 25 pub artists: Vec<String>, 26 pub artist_ids: Vec<String>, 27 pub album: Option<String>, 28 pub album_id: Option<String>, 29 pub album_artists: Vec<String>, 30 pub cover_url: Option<String>, 31 pub url: String, 32 pub added_at: Option<DateTime<Utc>>, 33 pub list_index: usize, 34 } 35 36 impl Track { from_simplified_track(track: &SimplifiedTrack, album: &FullAlbum) -> Track37 pub fn from_simplified_track(track: &SimplifiedTrack, album: &FullAlbum) -> Track { 38 let artists = track 39 .artists 40 .iter() 41 .map(|artist| artist.name.clone()) 42 .collect::<Vec<String>>(); 43 let artist_ids = track 44 .artists 45 .iter() 46 .filter_map(|a| a.id.as_ref().map(|id| id.id().to_string())) 47 .collect::<Vec<String>>(); 48 let album_artists = album 49 .artists 50 .iter() 51 .map(|artist| artist.name.clone()) 52 .collect::<Vec<String>>(); 53 54 Self { 55 id: track.id.as_ref().map(|id| id.id().to_string()), 56 uri: track.id.as_ref().map(|id| id.uri()).unwrap_or_default(), 57 title: track.name.clone(), 58 track_number: track.track_number, 59 disc_number: track.disc_number, 60 duration: track.duration.as_millis() as u32, 61 artists, 62 artist_ids, 63 album: Some(album.name.clone()), 64 album_id: Some(album.id.id().to_string()), 65 album_artists, 66 cover_url: album.images.get(0).map(|img| img.url.clone()), 67 url: track.id.as_ref().map(|id| id.url()).unwrap_or_default(), 68 added_at: None, 69 list_index: 0, 70 } 71 } 72 duration_str(&self) -> String73 pub fn duration_str(&self) -> String { 74 let minutes = self.duration / 60_000; 75 let seconds = (self.duration / 1000) % 60; 76 format!("{:02}:{:02}", minutes, seconds) 77 } 78 } 79 80 impl From<&SimplifiedTrack> for Track { from(track: &SimplifiedTrack) -> Self81 fn from(track: &SimplifiedTrack) -> Self { 82 let artists = track 83 .artists 84 .iter() 85 .map(|artist| artist.name.clone()) 86 .collect::<Vec<String>>(); 87 let artist_ids = track 88 .artists 89 .iter() 90 .filter_map(|a| a.id.as_ref().map(|a| a.id().to_string())) 91 .collect::<Vec<String>>(); 92 93 Self { 94 id: track.id.as_ref().map(|id| id.id().to_string()), 95 uri: track.id.as_ref().map(|id| id.uri()).unwrap_or_default(), 96 title: track.name.clone(), 97 track_number: track.track_number, 98 disc_number: track.disc_number, 99 duration: track.duration.as_millis() as u32, 100 artists, 101 artist_ids, 102 album: None, 103 album_id: None, 104 album_artists: Vec::new(), 105 cover_url: None, 106 url: track.id.as_ref().map(|id| id.url()).unwrap_or_default(), 107 added_at: None, 108 list_index: 0, 109 } 110 } 111 } 112 113 impl From<&FullTrack> for Track { from(track: &FullTrack) -> Self114 fn from(track: &FullTrack) -> Self { 115 let artists = track 116 .artists 117 .iter() 118 .map(|artist| artist.name.clone()) 119 .collect::<Vec<String>>(); 120 let artist_ids = track 121 .artists 122 .iter() 123 .filter_map(|a| a.id.as_ref().map(|a| a.id().to_string())) 124 .collect::<Vec<String>>(); 125 let album_artists = track 126 .album 127 .artists 128 .iter() 129 .map(|artist| artist.name.clone()) 130 .collect::<Vec<String>>(); 131 132 Self { 133 id: track.id.as_ref().map(|id| id.id().to_string()), 134 uri: track.id.as_ref().map(|id| id.uri()).unwrap_or_default(), 135 title: track.name.clone(), 136 track_number: track.track_number, 137 disc_number: track.disc_number, 138 duration: track.duration.as_millis() as u32, 139 artists, 140 artist_ids, 141 album: Some(track.album.name.clone()), 142 album_id: track.album.id.as_ref().map(|a| a.id().to_string()), 143 album_artists, 144 cover_url: track.album.images.get(0).map(|img| img.url.clone()), 145 url: track.id.as_ref().map(|id| id.url()).unwrap_or_default(), 146 added_at: None, 147 list_index: 0, 148 } 149 } 150 } 151 152 impl From<&SavedTrack> for Track { from(st: &SavedTrack) -> Self153 fn from(st: &SavedTrack) -> Self { 154 let mut track: Self = (&st.track).into(); 155 track.added_at = Some(st.added_at); 156 track 157 } 158 } 159 160 impl fmt::Display for Track { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result161 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 162 write!(f, "{} - {}", self.artists.join(", "), self.title) 163 } 164 } 165 166 impl fmt::Debug for Track { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result167 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 168 write!( 169 f, 170 "({} - {} ({:?}))", 171 self.artists.join(", "), 172 self.title, 173 self.id 174 ) 175 } 176 } 177 178 impl ListItem for Track { is_playing(&self, queue: Arc<Queue>) -> bool179 fn is_playing(&self, queue: Arc<Queue>) -> bool { 180 let current = queue.get_current(); 181 current.map(|t| t.id() == self.id).unwrap_or(false) 182 } 183 as_listitem(&self) -> Box<dyn ListItem>184 fn as_listitem(&self) -> Box<dyn ListItem> { 185 Box::new(self.clone()) 186 } 187 display_left(&self) -> String188 fn display_left(&self) -> String { 189 format!("{}", self) 190 } 191 display_center(&self, library: Arc<Library>) -> String192 fn display_center(&self, library: Arc<Library>) -> String { 193 if library.cfg.values().album_column.unwrap_or(true) { 194 self.album.clone().unwrap_or_default() 195 } else { 196 "".to_string() 197 } 198 } 199 display_right(&self, library: Arc<Library>) -> String200 fn display_right(&self, library: Arc<Library>) -> String { 201 let saved = if library.is_saved_track(&Playable::Track(self.clone())) { 202 if library.cfg.values().use_nerdfont.unwrap_or(false) { 203 "\u{f62b} " 204 } else { 205 "✓ " 206 } 207 } else { 208 "" 209 }; 210 format!("{}{}", saved, self.duration_str()) 211 } 212 play(&mut self, queue: Arc<Queue>)213 fn play(&mut self, queue: Arc<Queue>) { 214 let index = queue.append_next(&vec![Playable::Track(self.clone())]); 215 queue.play(index, true, false); 216 } 217 play_next(&mut self, queue: Arc<Queue>)218 fn play_next(&mut self, queue: Arc<Queue>) { 219 queue.insert_after_current(Playable::Track(self.clone())); 220 } 221 queue(&mut self, queue: Arc<Queue>)222 fn queue(&mut self, queue: Arc<Queue>) { 223 queue.append(Playable::Track(self.clone())); 224 } 225 save(&mut self, library: Arc<Library>)226 fn save(&mut self, library: Arc<Library>) { 227 library.save_tracks(vec![self], true); 228 } 229 unsave(&mut self, library: Arc<Library>)230 fn unsave(&mut self, library: Arc<Library>) { 231 library.unsave_tracks(vec![self], true); 232 } 233 toggle_saved(&mut self, library: Arc<Library>)234 fn toggle_saved(&mut self, library: Arc<Library>) { 235 if library.is_saved_track(&Playable::Track(self.clone())) { 236 library.unsave_tracks(vec![self], true); 237 } else { 238 library.save_tracks(vec![self], true); 239 } 240 } 241 open(&self, _queue: Arc<Queue>, _library: Arc<Library>) -> Option<Box<dyn ViewExt>>242 fn open(&self, _queue: Arc<Queue>, _library: Arc<Library>) -> Option<Box<dyn ViewExt>> { 243 None 244 } 245 open_recommendations( &mut self, queue: Arc<Queue>, library: Arc<Library>, ) -> Option<Box<dyn ViewExt>>246 fn open_recommendations( 247 &mut self, 248 queue: Arc<Queue>, 249 library: Arc<Library>, 250 ) -> Option<Box<dyn ViewExt>> { 251 let spotify = queue.get_spotify(); 252 253 let recommendations: Option<Vec<Track>> = if let Some(id) = &self.id { 254 spotify 255 .api 256 .recommendations(None, None, Some(vec![id])) 257 .map(|r| r.tracks) 258 .map(|tracks| tracks.iter().map(Track::from).collect()) 259 } else { 260 None 261 }; 262 263 recommendations.map(|tracks| { 264 ListView::new( 265 Arc::new(RwLock::new(tracks)), 266 queue.clone(), 267 library.clone(), 268 ) 269 .set_title(format!( 270 "Similar to \"{} - {}\"", 271 self.artists.join(", "), 272 self.title 273 )) 274 .into_boxed_view_ext() 275 }) 276 } 277 share_url(&self) -> Option<String>278 fn share_url(&self) -> Option<String> { 279 self.id 280 .clone() 281 .map(|id| format!("https://open.spotify.com/track/{}", id)) 282 } 283 album(&self, queue: Arc<Queue>) -> Option<Album>284 fn album(&self, queue: Arc<Queue>) -> Option<Album> { 285 let spotify = queue.get_spotify(); 286 287 match self.album_id { 288 Some(ref album_id) => spotify.api.album(album_id).map(|ref fa| fa.into()), 289 None => None, 290 } 291 } 292 artists(&self) -> Option<Vec<Artist>>293 fn artists(&self) -> Option<Vec<Artist>> { 294 Some( 295 self.artist_ids 296 .iter() 297 .zip(self.artists.iter()) 298 .map(|(id, name)| Artist::new(id.clone(), name.clone())) 299 .collect(), 300 ) 301 } 302 track(&self) -> Option<Track>303 fn track(&self) -> Option<Track> { 304 Some(self.clone()) 305 } 306 } 307