diff --git a/src/events.rs b/src/events.rs index b2deea2..2588538 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,16 +1,15 @@ use crossbeam_channel::{unbounded, Receiver, Sender, TryIter}; use cursive::{CbFunc, Cursive}; -use rspotify::spotify::model::track::FullTrack; -use spotify::PlayerStatus; - use queue::QueueChange; +use spotify::PlayerStatus; +use track::Track; use ui::playlist::PlaylistEvent; pub enum Event { Queue(QueueChange), PlayerStatus(PlayerStatus), - PlayerTrack(Option), + PlayerTrack(Option), Playlist(PlaylistEvent), } diff --git a/src/main.rs b/src/main.rs index 27a9ccb..f1b2670 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,6 +35,7 @@ mod events; mod queue; mod spotify; mod theme; +mod track; mod ui; use events::{Event, EventManager}; diff --git a/src/queue.rs b/src/queue.rs index 75d07bf..fae6c1b 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -1,12 +1,12 @@ use std::collections::vec_deque::Iter; use std::collections::VecDeque; -use rspotify::spotify::model::track::FullTrack; +use track::Track; use events::{Event, EventManager}; pub struct Queue { - queue: VecDeque, + queue: VecDeque, ev: EventManager, } @@ -24,35 +24,35 @@ impl Queue { ev: ev, } } - pub fn remove(&mut self, index: usize) -> Option { + pub fn remove(&mut self, index: usize) -> Option { match self.queue.remove(index) { Some(track) => { - debug!("Removed from queue: {}", &track.name); + debug!("Removed from queue: {}", &track); self.ev.send(Event::Queue(QueueChange::Remove(index))); Some(track) } None => None, } } - pub fn enqueue(&mut self, track: FullTrack) { - debug!("Queued: {}", &track.name); + pub fn enqueue(&mut self, track: Track) { + debug!("Queued: {}", &track); self.queue.push_back(track); self.ev.send(Event::Queue(QueueChange::Enqueue)); } - pub fn dequeue(&mut self) -> Option { + pub fn dequeue(&mut self) -> Option { match self.queue.pop_front() { Some(track) => { - debug!("Dequeued : {}", track.name); + debug!("Dequeued : {}", track); self.ev.send(Event::Queue(QueueChange::Dequeue)); Some(track) } None => None, } } - pub fn peek(&self) -> Option<&FullTrack> { + pub fn peek(&self) -> Option<&Track> { self.queue.get(0) } - pub fn iter(&self) -> Iter { + pub fn iter(&self) -> Iter { self.queue.iter() } } diff --git a/src/spotify.rs b/src/spotify.rs index 0a8943e..3763eeb 100644 --- a/src/spotify.rs +++ b/src/spotify.rs @@ -14,7 +14,6 @@ use rspotify::spotify::client::Spotify as SpotifyAPI; use rspotify::spotify::model::page::Page; use rspotify::spotify::model::playlist::{PlaylistTrack, SimplifiedPlaylist}; use rspotify::spotify::model::search::SearchTracks; -use rspotify::spotify::model::track::FullTrack; use failure::Error; @@ -34,9 +33,10 @@ use std::time::{Duration, SystemTime}; use events::{Event, EventManager}; use queue::Queue; +use track::Track; enum WorkerCommand { - Load(FullTrack), + Load(Track), Play, Pause, Stop, @@ -51,7 +51,7 @@ pub enum PlayerStatus { pub struct Spotify { status: RwLock, - track: RwLock>, + track: RwLock>, pub api: SpotifyAPI, elapsed: RwLock>, since: RwLock>, @@ -99,9 +99,7 @@ impl futures::Future for Worker { debug!("message received!"); match cmd { WorkerCommand::Load(track) => { - let trackid = - SpotifyId::from_base62(&track.id).expect("could not load track"); - self.play_task = Box::new(self.player.load(trackid, false, 0)); + self.play_task = Box::new(self.player.load(track.id, false, 0)); info!("player loading track.."); self.events.send(Event::PlayerTrack(Some(track))); } @@ -127,10 +125,8 @@ impl futures::Future for Worker { let mut queue = self.queue.lock().unwrap(); if let Some(track) = queue.dequeue() { - debug!("next track in queue: {}", track.name); - let trackid = - SpotifyId::from_base62(&track.id).expect("could not load track"); - self.play_task = Box::new(self.player.load(trackid, false, 0)); + debug!("next track in queue: {}", track); + self.play_task = Box::new(self.player.load(track.id, false, 0)); self.player.play(); self.events.send(Event::PlayerTrack(Some(track))); @@ -246,7 +242,7 @@ impl Spotify { (*status).clone() } - pub fn get_current_track(&self) -> Option { + pub fn get_current_track(&self) -> Option { let track = self .track .read() @@ -311,7 +307,7 @@ impl Spotify { .user_playlist_tracks(&self.user, playlist_id, None, 50, 0, None) } - pub fn load(&self, track: FullTrack) { + pub fn load(&self, track: Track) { info!("loading track: {:?}", track); self.channel .unbounded_send(WorkerCommand::Load(track)) @@ -340,7 +336,7 @@ impl Spotify { *status = new_status; } - pub fn update_track(&self, new_track: Option) { + pub fn update_track(&self, new_track: Option) { self.set_elapsed(None); self.set_since(None); diff --git a/src/track.rs b/src/track.rs new file mode 100644 index 0000000..9c367ec --- /dev/null +++ b/src/track.rs @@ -0,0 +1,54 @@ +use std::fmt; + +use librespot::core::spotify_id::SpotifyId; +use rspotify::spotify::model::track::FullTrack; + +#[derive(Clone)] +pub struct Track { + pub id: SpotifyId, + pub duration: u32, + pub artists: String, + pub title: String, +} + +impl Track { + pub fn new(track: &FullTrack) -> Track { + let artists_joined = track + .artists + .iter() + .map(|ref artist| artist.name.clone()) + .collect::>() + .join(", "); + + Track { + id: SpotifyId::from_base62(&track.id).expect("could not load track"), + duration: track.duration_ms / 1000, + artists: artists_joined, + title: track.name.clone(), + } + } + + pub fn duration_str(&self) -> String { + let minutes = self.duration / 60; + let seconds = self.duration % 60; + format!("{:02}:{:02}", minutes, seconds) + } +} + +impl fmt::Display for Track { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} - {}", self.artists, self.title) + } +} + +impl fmt::Debug for Track { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "({} - {} ({})", + self.artists, + self.title, + self.id.to_base62() + ) + } +} diff --git a/src/ui/playlist.rs b/src/ui/playlist.rs index ff2c570..7be9c6c 100644 --- a/src/ui/playlist.rs +++ b/src/ui/playlist.rs @@ -9,6 +9,7 @@ use rspotify::spotify::model::playlist::SimplifiedPlaylist; use queue::Queue; use spotify::Spotify; +use track::Track; pub enum PlaylistEvent { Refresh, @@ -49,7 +50,7 @@ impl PlaylistView { let tracks = spotify_ref.user_playlist_tracks(&id).unwrap().items; let mut locked_queue = queue_ref.lock().expect("Could not aquire lock"); for playlist_track in tracks { - locked_queue.enqueue(playlist_track.track.clone()); + locked_queue.enqueue(Track::new(&playlist_track.track)); } }); diff --git a/src/ui/queue.rs b/src/ui/queue.rs index 82bb708..243ba67 100644 --- a/src/ui/queue.rs +++ b/src/ui/queue.rs @@ -8,10 +8,9 @@ use cursive::Cursive; use std::sync::Arc; use std::sync::Mutex; -use rspotify::spotify::model::track::FullTrack; - use queue::{Queue, QueueChange}; use spotify::Spotify; +use track::Track; use ui::trackbutton::TrackButton; pub struct QueueView { @@ -72,7 +71,7 @@ impl QueueView { } } - fn create_button(&self, track: &FullTrack) -> TrackButton { + fn create_button(&self, track: &Track) -> TrackButton { let mut button = TrackButton::new(&track); // 'd' deletes the selected track { diff --git a/src/ui/search.rs b/src/ui/search.rs index 5709f92..0e61ef0 100644 --- a/src/ui/search.rs +++ b/src/ui/search.rs @@ -9,6 +9,7 @@ use std::sync::Mutex; use queue::Queue; use spotify::Spotify; +use track::Track; use ui::trackbutton::TrackButton; pub struct SearchView { @@ -29,7 +30,9 @@ impl SearchView { results.clear(); if let Ok(tracks) = tracks { - for track in tracks.tracks.items { + for search_track in tracks.tracks.items { + let track = Track::new(&search_track); + let s = spotify.clone(); let mut button = TrackButton::new(&track); diff --git a/src/ui/statusbar.rs b/src/ui/statusbar.rs index b1be361..6c48673 100644 --- a/src/ui/statusbar.rs +++ b/src/ui/statusbar.rs @@ -51,21 +51,6 @@ impl View for StatusBar { }); if let Some(ref t) = self.spotify.get_current_track() { - let name = format!( - "{} - {}", - t.artists - .iter() - .map(|ref artist| artist.name.clone()) - .collect::>() - .join(", "), - t.name - ) - .to_string(); - - let minutes = t.duration_ms / 60000; - let seconds = (t.duration_ms % 60000) / 1000; - let formatted_duration = format!("{:02}:{:02}", minutes, seconds); - let elapsed = self.spotify.get_current_progress(); let formatted_elapsed = format!( "{:02}:{:02}", @@ -73,18 +58,18 @@ impl View for StatusBar { elapsed.as_secs() % 60 ); - let duration = format!("{} / {} ", formatted_elapsed, formatted_duration); + let duration = format!("{} / {} ", formatted_elapsed, t.duration_str()); let offset = HAlign::Right.get_offset(duration.width(), printer.size.x); printer.with_color(style, |printer| { - printer.print((4, 1), &name); + printer.print((4, 1), &t.to_string()); printer.print((offset, 1), &duration); }); printer.with_color(style_bar, |printer| { printer.print((0, 0), &"—".repeat(printer.size.x)); - let duration_width = (((printer.size.x as u32) * (elapsed.as_millis() as u32)) - / t.duration_ms) as usize; + let duration_width = + (((printer.size.x as u32) * (elapsed.as_secs() as u32)) / t.duration) as usize; printer.print((0, 0), &format!("{}{}", "=".repeat(duration_width), ">")); }); } else { diff --git a/src/ui/trackbutton.rs b/src/ui/trackbutton.rs index 208abc7..24fe61e 100644 --- a/src/ui/trackbutton.rs +++ b/src/ui/trackbutton.rs @@ -6,15 +6,14 @@ use cursive::traits::View; use cursive::vec::Vec2; use cursive::Cursive; use cursive::Printer; -use rspotify::spotify::model::track::FullTrack; use unicode_width::UnicodeWidthStr; +use track::Track; + pub struct TrackButton { callbacks: Vec<(EventTrigger, Callback)>, - track: FullTrack, - title: String, - duration: String, + track: Track, enabled: bool, last_size: Vec2, @@ -22,24 +21,10 @@ pub struct TrackButton { } impl TrackButton { - pub fn new(track: &FullTrack) -> TrackButton { - let artists = track - .artists - .iter() - .map(|ref artist| artist.name.clone()) - .collect::>() - .join(", "); - let formatted_title = format!("{} - {}", artists, track.name); - - let minutes = track.duration_ms / 60000; - let seconds = (track.duration_ms % 60000) / 1000; - let formatted_duration = format!("{:02}:{:02}", minutes, seconds); - + pub fn new(track: &Track) -> TrackButton { TrackButton { callbacks: Vec::new(), track: track.clone(), - title: formatted_title, - duration: formatted_duration, enabled: true, last_size: Vec2::zero(), invalidated: true, @@ -72,9 +57,9 @@ impl View for TrackButton { }; // shorten titles that are too long and append ".." to indicate this - let mut title_shortened = self.title.clone(); - title_shortened.truncate(printer.size.x - self.duration.width() - 1); - if title_shortened.width() < self.title.width() { + let mut title_shortened = self.track.to_string(); + title_shortened.truncate(printer.size.x - self.track.duration_str().width() - 1); + if title_shortened.width() < self.track.to_string().width() { let offset = title_shortened.width() - 2; title_shortened.replace_range(offset.., ".."); } @@ -84,10 +69,10 @@ impl View for TrackButton { }); // track duration goes to the end of the line - let offset = HAlign::Right.get_offset(self.duration.width(), printer.size.x); + let offset = HAlign::Right.get_offset(self.track.duration_str().width(), printer.size.x); printer.with_color(style, |printer| { - printer.print((offset, 0), &self.duration); + printer.print((offset, 0), &self.track.duration_str()); }); }