Refactor: move playable models to separate module
This commit is contained in:
306
src/model/track.rs
Normal file
306
src/model/track.rs
Normal file
@@ -0,0 +1,306 @@
|
||||
use std::fmt;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use rspotify::model::album::FullAlbum;
|
||||
use rspotify::model::track::{FullTrack, SavedTrack, SimplifiedTrack};
|
||||
use rspotify::model::Id;
|
||||
|
||||
use crate::library::Library;
|
||||
use crate::model::album::Album;
|
||||
use crate::model::artist::Artist;
|
||||
use crate::model::playable::Playable;
|
||||
use crate::queue::Queue;
|
||||
use crate::traits::{IntoBoxedViewExt, ListItem, ViewExt};
|
||||
use crate::ui::listview::ListView;
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct Track {
|
||||
pub id: Option<String>,
|
||||
pub uri: String,
|
||||
pub title: String,
|
||||
pub track_number: u32,
|
||||
pub disc_number: i32,
|
||||
pub duration: u32,
|
||||
pub artists: Vec<String>,
|
||||
pub artist_ids: Vec<String>,
|
||||
pub album: Option<String>,
|
||||
pub album_id: Option<String>,
|
||||
pub album_artists: Vec<String>,
|
||||
pub cover_url: Option<String>,
|
||||
pub url: String,
|
||||
pub added_at: Option<DateTime<Utc>>,
|
||||
pub list_index: usize,
|
||||
}
|
||||
|
||||
impl Track {
|
||||
pub fn from_simplified_track(track: &SimplifiedTrack, album: &FullAlbum) -> Track {
|
||||
let artists = track
|
||||
.artists
|
||||
.iter()
|
||||
.map(|artist| artist.name.clone())
|
||||
.collect::<Vec<String>>();
|
||||
let artist_ids = track
|
||||
.artists
|
||||
.iter()
|
||||
.filter_map(|a| a.id.as_ref().map(|id| id.id().to_string()))
|
||||
.collect::<Vec<String>>();
|
||||
let album_artists = album
|
||||
.artists
|
||||
.iter()
|
||||
.map(|artist| artist.name.clone())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
Self {
|
||||
id: track.id.as_ref().map(|id| id.id().to_string()),
|
||||
uri: track.id.as_ref().map(|id| id.uri()).unwrap_or_default(),
|
||||
title: track.name.clone(),
|
||||
track_number: track.track_number,
|
||||
disc_number: track.disc_number,
|
||||
duration: track.duration.as_millis() as u32,
|
||||
artists,
|
||||
artist_ids,
|
||||
album: Some(album.name.clone()),
|
||||
album_id: Some(album.id.id().to_string()),
|
||||
album_artists,
|
||||
cover_url: album.images.get(0).map(|img| img.url.clone()),
|
||||
url: track.id.as_ref().map(|id| id.url()).unwrap_or_default(),
|
||||
added_at: None,
|
||||
list_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duration_str(&self) -> String {
|
||||
let minutes = self.duration / 60_000;
|
||||
let seconds = (self.duration / 1000) % 60;
|
||||
format!("{:02}:{:02}", minutes, seconds)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SimplifiedTrack> for Track {
|
||||
fn from(track: &SimplifiedTrack) -> Self {
|
||||
let artists = track
|
||||
.artists
|
||||
.iter()
|
||||
.map(|artist| artist.name.clone())
|
||||
.collect::<Vec<String>>();
|
||||
let artist_ids = track
|
||||
.artists
|
||||
.iter()
|
||||
.filter_map(|a| a.id.as_ref().map(|a| a.id().to_string()))
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
Self {
|
||||
id: track.id.as_ref().map(|id| id.id().to_string()),
|
||||
uri: track.id.as_ref().map(|id| id.uri()).unwrap_or_default(),
|
||||
title: track.name.clone(),
|
||||
track_number: track.track_number,
|
||||
disc_number: track.disc_number,
|
||||
duration: track.duration.as_millis() as u32,
|
||||
artists,
|
||||
artist_ids,
|
||||
album: None,
|
||||
album_id: None,
|
||||
album_artists: Vec::new(),
|
||||
cover_url: None,
|
||||
url: track.id.as_ref().map(|id| id.url()).unwrap_or_default(),
|
||||
added_at: None,
|
||||
list_index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&FullTrack> for Track {
|
||||
fn from(track: &FullTrack) -> Self {
|
||||
let artists = track
|
||||
.artists
|
||||
.iter()
|
||||
.map(|artist| artist.name.clone())
|
||||
.collect::<Vec<String>>();
|
||||
let artist_ids = track
|
||||
.artists
|
||||
.iter()
|
||||
.filter_map(|a| a.id.as_ref().map(|a| a.id().to_string()))
|
||||
.collect::<Vec<String>>();
|
||||
let album_artists = track
|
||||
.album
|
||||
.artists
|
||||
.iter()
|
||||
.map(|artist| artist.name.clone())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
Self {
|
||||
id: Some(track.id.id().to_string()),
|
||||
uri: track.id.uri(),
|
||||
title: track.name.clone(),
|
||||
track_number: track.track_number,
|
||||
disc_number: track.disc_number,
|
||||
duration: track.duration.as_millis() as u32,
|
||||
artists,
|
||||
artist_ids,
|
||||
album: Some(track.album.name.clone()),
|
||||
album_id: track.album.id.as_ref().map(|a| a.id().to_string()),
|
||||
album_artists,
|
||||
cover_url: track.album.images.get(0).map(|img| img.url.clone()),
|
||||
url: track.id.url(),
|
||||
added_at: None,
|
||||
list_index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SavedTrack> for Track {
|
||||
fn from(st: &SavedTrack) -> Self {
|
||||
let mut track: Self = (&st.track).into();
|
||||
track.added_at = Some(st.added_at);
|
||||
track
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Track {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} - {}", self.artists.join(", "), self.title)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Track {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"({} - {} ({:?}))",
|
||||
self.artists.join(", "),
|
||||
self.title,
|
||||
self.id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ListItem for Track {
|
||||
fn is_playing(&self, queue: Arc<Queue>) -> bool {
|
||||
let current = queue.get_current();
|
||||
current.map(|t| t.id() == self.id).unwrap_or(false)
|
||||
}
|
||||
|
||||
fn as_listitem(&self) -> Box<dyn ListItem> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn display_left(&self) -> String {
|
||||
format!("{}", self)
|
||||
}
|
||||
|
||||
fn display_center(&self, library: Arc<Library>) -> String {
|
||||
if library.cfg.values().album_column.unwrap_or(true) {
|
||||
self.album.clone().unwrap_or_default()
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn display_right(&self, library: Arc<Library>) -> String {
|
||||
let saved = if library.is_saved_track(&Playable::Track(self.clone())) {
|
||||
if library.cfg.values().use_nerdfont.unwrap_or(false) {
|
||||
"\u{f62b} "
|
||||
} else {
|
||||
"✓ "
|
||||
}
|
||||
} else {
|
||||
""
|
||||
};
|
||||
format!("{}{}", saved, self.duration_str())
|
||||
}
|
||||
|
||||
fn play(&mut self, queue: Arc<Queue>) {
|
||||
let index = queue.append_next(&vec![Playable::Track(self.clone())]);
|
||||
queue.play(index, true, false);
|
||||
}
|
||||
|
||||
fn play_next(&mut self, queue: Arc<Queue>) {
|
||||
queue.insert_after_current(Playable::Track(self.clone()));
|
||||
}
|
||||
|
||||
fn queue(&mut self, queue: Arc<Queue>) {
|
||||
queue.append(Playable::Track(self.clone()));
|
||||
}
|
||||
|
||||
fn save(&mut self, library: Arc<Library>) {
|
||||
library.save_tracks(vec![self], true);
|
||||
}
|
||||
|
||||
fn unsave(&mut self, library: Arc<Library>) {
|
||||
library.unsave_tracks(vec![self], true);
|
||||
}
|
||||
|
||||
fn toggle_saved(&mut self, library: Arc<Library>) {
|
||||
if library.is_saved_track(&Playable::Track(self.clone())) {
|
||||
library.unsave_tracks(vec![self], true);
|
||||
} else {
|
||||
library.save_tracks(vec![self], true);
|
||||
}
|
||||
}
|
||||
|
||||
fn open(&self, _queue: Arc<Queue>, _library: Arc<Library>) -> Option<Box<dyn ViewExt>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn open_recommendations(
|
||||
&mut self,
|
||||
queue: Arc<Queue>,
|
||||
library: Arc<Library>,
|
||||
) -> Option<Box<dyn ViewExt>> {
|
||||
let spotify = queue.get_spotify();
|
||||
|
||||
let recommendations: Option<Vec<Track>> = if let Some(id) = &self.id {
|
||||
spotify
|
||||
.api
|
||||
.recommendations(None, None, Some(vec![id]))
|
||||
.map(|r| r.tracks)
|
||||
.map(|tracks| tracks.iter().map(Track::from).collect())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
recommendations.map(|tracks| {
|
||||
ListView::new(
|
||||
Arc::new(RwLock::new(tracks)),
|
||||
queue.clone(),
|
||||
library.clone(),
|
||||
)
|
||||
.set_title(format!(
|
||||
"Similar to \"{} - {}\"",
|
||||
self.artists.join(", "),
|
||||
self.title
|
||||
))
|
||||
.into_boxed_view_ext()
|
||||
})
|
||||
}
|
||||
|
||||
fn share_url(&self) -> Option<String> {
|
||||
self.id
|
||||
.clone()
|
||||
.map(|id| format!("https://open.spotify.com/track/{}", id))
|
||||
}
|
||||
|
||||
fn album(&self, queue: Arc<Queue>) -> Option<Album> {
|
||||
let spotify = queue.get_spotify();
|
||||
|
||||
match self.album_id {
|
||||
Some(ref album_id) => spotify.api.album(album_id).map(|ref fa| fa.into()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn artists(&self) -> Option<Vec<Artist>> {
|
||||
Some(
|
||||
self.artist_ids
|
||||
.iter()
|
||||
.zip(self.artists.iter())
|
||||
.map(|(id, name)| Artist::new(id.clone(), name.clone()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn track(&self) -> Option<Track> {
|
||||
Some(self.clone())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user