diff --git a/src/library.rs b/src/library.rs index b8394b9..bb1e04e 100644 --- a/src/library.rs +++ b/src/library.rs @@ -489,6 +489,31 @@ impl Library { } } + pub fn playlist_append_tracks(&self, playlist_id: &str, new_tracks: &[Track]) { + let track_ids: Vec = new_tracks + .to_vec() + .iter() + .filter(|t| t.id.is_some()) + .map(|t| t.id.clone().unwrap()) + .collect(); + + let mut has_modified = false; + + if self.spotify.append_tracks(playlist_id, &track_ids, None) { + let mut playlists = self.playlists.write().expect("can't writelock playlists"); + if let Some(playlist) = playlists.iter_mut().find(|p| p.id == playlist_id) { + if let Some(tracks) = &mut playlist.tracks { + tracks.append(&mut new_tracks.to_vec()); + has_modified = true; + } + } + } + + if has_modified { + self.save_cache(config::cache_path(CACHE_PLAYLISTS), self.playlists.clone()); + } + } + pub fn is_saved_track(&self, track: &Track) -> bool { if !*self.is_done.read().unwrap() { return false; diff --git a/src/spotify.rs b/src/spotify.rs index 121f63f..529cc9a 100644 --- a/src/spotify.rs +++ b/src/spotify.rs @@ -415,6 +415,18 @@ impl Spotify { } } + pub fn append_tracks( + &self, + playlist_id: &str, + tracks: &[String], + position: Option, + ) -> bool { + self.api_with_retry(|api| { + api.user_playlist_add_tracks(&self.user, playlist_id, &tracks, position) + }) + .is_some() + } + pub fn overwrite_playlist(&self, id: &str, tracks: &[Track]) { // extract only track IDs let mut tracks: Vec = tracks @@ -443,11 +455,7 @@ impl Spotify { }; debug!("adding another {} tracks to playlist", tracks.len()); - let result = self.api_with_retry(|api| { - api.user_playlist_add_tracks(&self.user, id, &tracks, None) - }); - - if result.is_some() { + if self.append_tracks(id, tracks, None) { debug!("{} tracks successfully added", tracks.len()); } else { error!("error saving tracks to playlists {}", id); diff --git a/src/track.rs b/src/track.rs index 79d3b9f..ac5b57f 100644 --- a/src/track.rs +++ b/src/track.rs @@ -222,4 +222,8 @@ impl ListItem for Track { self.artists[0].clone(), )) } + + fn track(&self) -> Option { + Some(self.clone()) + } } diff --git a/src/traits.rs b/src/traits.rs index a13fbaf..e4c8301 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -10,6 +10,7 @@ use command::Command; use commands::CommandResult; use library::Library; use queue::Queue; +use track::Track; pub trait ListItem: Sync + Send + 'static { fn is_playing(&self, queue: Arc) -> bool; @@ -31,6 +32,10 @@ pub trait ListItem: Sync + Send + 'static { None } + fn track(&self) -> Option { + None + } + fn as_listitem(&self) -> Box; } diff --git a/src/ui/contextmenu.rs b/src/ui/contextmenu.rs index daa5199..26bff6a 100644 --- a/src/ui/contextmenu.rs +++ b/src/ui/contextmenu.rs @@ -1,13 +1,14 @@ use std::sync::Arc; use cursive::view::ViewWrapper; -use cursive::views::{Dialog, SelectView}; +use cursive::views::{Dialog, ScrollView, SelectView}; use cursive::Cursive; #[cfg(feature = "share_clipboard")] use clipboard::{ClipboardContext, ClipboardProvider}; use library::Library; use queue::Queue; +use track::Track; use traits::ListItem; use ui::layout::Layout; use ui::modal::Modal; @@ -19,9 +20,30 @@ pub struct ContextMenu { enum ContextMenuAction { ShowItem(Box), ShareUrl(String), + AddToPlaylist(Box), } impl ContextMenu { + pub fn add_track_dialog(library: Arc, track: Track) -> Modal { + let mut list_select: SelectView = SelectView::new().autojump(); + + for list in library.items().iter() { + list_select.add_item(list.name.clone(), list.id.clone()); + } + + list_select.set_on_submit(move |s, selected| { + library.playlist_append_tracks(selected, &[track.clone()]); + s.pop_layer(); + }); + + let dialog = Dialog::new() + .title("Add track to playlist") + .dismiss_button("Cancel") + .padding((1, 1, 1, 0)) + .content(ScrollView::new(list_select)); + Modal::new(dialog) + } + pub fn new(item: &dyn ListItem, queue: Arc, library: Arc) -> Self { let mut content: SelectView = SelectView::new().autojump(); if let Some(a) = item.artist() { @@ -34,27 +56,33 @@ impl ContextMenu { #[cfg(feature = "share_clipboard")] content.add_item("Share", ContextMenuAction::ShareUrl(url)); } + if let Some(t) = item.track() { + content.add_item("Add to playlist", ContextMenuAction::AddToPlaylist(Box::new(t))) + } // open detail view of artist/album content.set_on_submit(move |s: &mut Cursive, action: &ContextMenuAction| { s.pop_layer(); let queue = queue.clone(); let library = library.clone(); - s.call_on_id("main", move |v: &mut Layout| match action { + + match action { ContextMenuAction::ShowItem(item) => { if let Some(view) = item.open(queue, library) { - v.push_view(view) + s.call_on_id("main", move |v: &mut Layout| v.push_view(view)); } } ContextMenuAction::ShareUrl(url) => { #[cfg(feature = "share_clipboard")] - { - ClipboardProvider::new() - .and_then(|mut ctx: ClipboardContext| ctx.set_contents(url.to_string())) - .ok(); - } + ClipboardProvider::new() + .and_then(|mut ctx: ClipboardContext| ctx.set_contents(url.to_string())) + .ok(); } - }); + ContextMenuAction::AddToPlaylist(track) => { + let dialog = Self::add_track_dialog(library, *track.clone()); + s.add_layer(dialog); + } + } }); let dialog = Dialog::new()