fix: update playlist changes in local store
Move playlist change logic out of the library while we're at it and notify the library of changes instead. fixes #302
This commit is contained in:
@@ -523,41 +523,14 @@ impl Library {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return true if the given playlist contains the given track
|
pub fn playlist_update(&self, updated: &Playlist) {
|
||||||
pub fn playlist_has_track(&self, playlist_id: &str, track_id: &str) -> bool {
|
{
|
||||||
let playlists = self.playlists.read().expect("can't readlock playlists");
|
|
||||||
if let Some(playlist) = playlists.iter().find(|p| p.id == playlist_id) {
|
|
||||||
playlist.tracks.as_ref().map_or(false, |tracks| {
|
|
||||||
tracks.iter().any(|t| t.id == Some(track_id.to_string()))
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn playlist_append_tracks(&self, playlist_id: &str, new_tracks: &[Track]) {
|
|
||||||
let track_ids: Vec<String> = 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");
|
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(playlist) = playlists.iter_mut().find(|p| p.id == updated.id) {
|
||||||
if let Some(tracks) = &mut playlist.tracks {
|
*playlist = updated.clone();
|
||||||
tracks.append(&mut new_tracks.to_vec());
|
|
||||||
has_modified = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.save_cache(config::cache_path(CACHE_PLAYLISTS), self.playlists.clone());
|
||||||
if has_modified {
|
|
||||||
self.save_cache(config::cache_path(CACHE_PLAYLISTS), self.playlists.clone());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_saved_track(&self, track: &Playable) -> bool {
|
pub fn is_saved_track(&self, track: &Playable) -> bool {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ extern crate tokio_timer;
|
|||||||
extern crate unicode_width;
|
extern crate unicode_width;
|
||||||
extern crate webbrowser;
|
extern crate webbrowser;
|
||||||
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
extern crate dbus_tree;
|
|
||||||
extern crate dbus;
|
extern crate dbus;
|
||||||
|
extern crate dbus_tree;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@@ -9,8 +9,8 @@ use std::time::Duration;
|
|||||||
use dbus::arg::{RefArg, Variant};
|
use dbus::arg::{RefArg, Variant};
|
||||||
use dbus::ffidisp::stdintf::org_freedesktop_dbus::PropertiesPropertiesChanged;
|
use dbus::ffidisp::stdintf::org_freedesktop_dbus::PropertiesPropertiesChanged;
|
||||||
use dbus::message::SignalArgs;
|
use dbus::message::SignalArgs;
|
||||||
use dbus_tree::{Access, Factory};
|
|
||||||
use dbus::strings::Path;
|
use dbus::strings::Path;
|
||||||
|
use dbus_tree::{Access, Factory};
|
||||||
|
|
||||||
use crate::album::Album;
|
use crate::album::Album;
|
||||||
use crate::episode::Episode;
|
use crate::episode::Episode;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ impl Playlist {
|
|||||||
self.tracks = Some(self.get_all_tracks(spotify));
|
self.tracks = Some(self.get_all_tracks(spotify));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_all_tracks(&self, spotify: Arc<Spotify>) -> Vec<Track> {
|
fn get_all_tracks(&self, spotify: Arc<Spotify>) -> Vec<Track> {
|
||||||
let mut collected_tracks = Vec::new();
|
let mut collected_tracks = Vec::new();
|
||||||
|
|
||||||
let mut tracks_result = spotify.user_playlist_tracks(&self.id, 100, 0);
|
let mut tracks_result = spotify.user_playlist_tracks(&self.id, 100, 0);
|
||||||
@@ -59,15 +59,56 @@ impl Playlist {
|
|||||||
collected_tracks
|
collected_tracks
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_tracks(&mut self, track_pos_pairs: &[(Track, usize)], spotify: Arc<Spotify>) {
|
pub fn has_track(&self, track_id: &str) -> bool {
|
||||||
|
self.tracks.as_ref().map_or(false, |tracks| {
|
||||||
|
tracks
|
||||||
|
.iter()
|
||||||
|
.any(|track| track.id == Some(track_id.to_string()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_tracks(
|
||||||
|
&mut self,
|
||||||
|
track_pos_pairs: &[(Track, usize)],
|
||||||
|
spotify: Arc<Spotify>,
|
||||||
|
library: Arc<Library>,
|
||||||
|
) {
|
||||||
if spotify.delete_tracks(&self.id, track_pos_pairs) {
|
if spotify.delete_tracks(&self.id, track_pos_pairs) {
|
||||||
if let Some(tracks) = &mut self.tracks {
|
if let Some(tracks) = &mut self.tracks {
|
||||||
for (_track, pos) in track_pos_pairs {
|
for (_track, pos) in track_pos_pairs {
|
||||||
tracks.remove(*pos);
|
tracks.remove(*pos);
|
||||||
}
|
}
|
||||||
|
library.playlist_update(&self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn append_tracks(
|
||||||
|
&mut self,
|
||||||
|
new_tracks: &[Track],
|
||||||
|
spotify: Arc<Spotify>,
|
||||||
|
library: Arc<Library>,
|
||||||
|
) {
|
||||||
|
let track_ids: Vec<String> = new_tracks
|
||||||
|
.to_vec()
|
||||||
|
.iter()
|
||||||
|
.filter(|t| t.id.is_some())
|
||||||
|
.map(|t| t.id.clone().unwrap())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut has_modified = false;
|
||||||
|
|
||||||
|
if spotify.append_tracks(&self.id, &track_ids, None) {
|
||||||
|
if let Some(tracks) = &mut self.tracks {
|
||||||
|
tracks.append(&mut new_tracks.to_vec());
|
||||||
|
has_modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_modified {
|
||||||
|
library.playlist_update(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&SimplifiedPlaylist> for Playlist {
|
impl From<&SimplifiedPlaylist> for Playlist {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use cursive::view::{Margins, ViewWrapper};
|
|||||||
use cursive::views::{Dialog, NamedView, ScrollView, SelectView};
|
use cursive::views::{Dialog, NamedView, ScrollView, SelectView};
|
||||||
use cursive::Cursive;
|
use cursive::Cursive;
|
||||||
|
|
||||||
use crate::command::{Command, MoveAmount, MoveMode};
|
|
||||||
use crate::commands::CommandResult;
|
use crate::commands::CommandResult;
|
||||||
use crate::library::Library;
|
use crate::library::Library;
|
||||||
use crate::queue::Queue;
|
use crate::queue::Queue;
|
||||||
@@ -12,6 +11,11 @@ use crate::track::Track;
|
|||||||
use crate::traits::{ListItem, ViewExt};
|
use crate::traits::{ListItem, ViewExt};
|
||||||
use crate::ui::layout::Layout;
|
use crate::ui::layout::Layout;
|
||||||
use crate::ui::modal::Modal;
|
use crate::ui::modal::Modal;
|
||||||
|
use crate::{
|
||||||
|
command::{Command, MoveAmount, MoveMode},
|
||||||
|
playlist::Playlist,
|
||||||
|
spotify::Spotify,
|
||||||
|
};
|
||||||
#[cfg(feature = "share_clipboard")]
|
#[cfg(feature = "share_clipboard")]
|
||||||
use clipboard::{ClipboardContext, ClipboardProvider};
|
use clipboard::{ClipboardContext, ClipboardProvider};
|
||||||
use cursive::traits::{Finder, Nameable};
|
use cursive::traits::{Finder, Nameable};
|
||||||
@@ -28,23 +32,33 @@ enum ContextMenuAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ContextMenu {
|
impl ContextMenu {
|
||||||
pub fn add_track_dialog(library: Arc<Library>, track: Track) -> Modal<Dialog> {
|
pub fn add_track_dialog(
|
||||||
let mut list_select: SelectView<String> = SelectView::new().autojump();
|
library: Arc<Library>,
|
||||||
|
spotify: Arc<Spotify>,
|
||||||
|
track: Track,
|
||||||
|
) -> Modal<Dialog> {
|
||||||
|
let mut list_select: SelectView<Playlist> = SelectView::new().autojump();
|
||||||
|
|
||||||
for list in library.items().iter() {
|
for list in library.items().iter() {
|
||||||
list_select.add_item(list.name.clone(), list.id.clone());
|
list_select.add_item(list.name.clone(), list.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
list_select.set_on_submit(move |s, selected| {
|
list_select.set_on_submit(move |s, selected| {
|
||||||
let track = track.clone();
|
let track = track.clone();
|
||||||
|
let mut playlist = selected.clone();
|
||||||
|
let spotify = spotify.clone();
|
||||||
|
let library = library.clone();
|
||||||
|
|
||||||
if library.playlist_has_track(selected, &track.id.as_ref().unwrap_or(&"".to_string())) {
|
if playlist.has_track(track.id.as_ref().unwrap_or(&String::new())) {
|
||||||
let mut already_added_dialog = Self::track_already_added();
|
let mut already_added_dialog = Self::track_already_added();
|
||||||
|
|
||||||
let lib = Arc::clone(&library);
|
|
||||||
let selected = selected.to_string();
|
|
||||||
already_added_dialog.add_button("Add anyway", move |c| {
|
already_added_dialog.add_button("Add anyway", move |c| {
|
||||||
lib.playlist_append_tracks(&selected.clone(), &[track.clone()]);
|
let mut playlist = playlist.clone();
|
||||||
|
let spotify = spotify.clone();
|
||||||
|
let library = library.clone();
|
||||||
|
|
||||||
|
playlist.append_tracks(&[track.clone()], spotify, library);
|
||||||
|
// playlist.map(|p| p.append_tracks(&[track.clone()], spotify, library));
|
||||||
c.pop_layer();
|
c.pop_layer();
|
||||||
|
|
||||||
// Close add_track_dialog too
|
// Close add_track_dialog too
|
||||||
@@ -55,10 +69,10 @@ impl ContextMenu {
|
|||||||
s.add_layer(modal);
|
s.add_layer(modal);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
playlist.append_tracks(&[track.clone()], spotify, library);
|
||||||
|
s.pop_layer();
|
||||||
}
|
}
|
||||||
|
|
||||||
library.playlist_append_tracks(selected, &[track]);
|
|
||||||
s.pop_layer();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let dialog = Dialog::new()
|
let dialog = Dialog::new()
|
||||||
@@ -118,7 +132,8 @@ impl ContextMenu {
|
|||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
ContextMenuAction::AddToPlaylist(track) => {
|
ContextMenuAction::AddToPlaylist(track) => {
|
||||||
let dialog = Self::add_track_dialog(library, *track.clone());
|
let dialog =
|
||||||
|
Self::add_track_dialog(library, queue.get_spotify(), *track.clone());
|
||||||
s.add_layer(dialog);
|
s.add_layer(dialog);
|
||||||
}
|
}
|
||||||
ContextMenuAction::ShowRecommentations(item) => {
|
ContextMenuAction::ShowRecommentations(item) => {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ pub struct PlaylistView {
|
|||||||
playlist: Playlist,
|
playlist: Playlist,
|
||||||
list: ListView<Track>,
|
list: ListView<Track>,
|
||||||
spotify: Arc<Spotify>,
|
spotify: Arc<Spotify>,
|
||||||
|
library: Arc<Library>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlaylistView {
|
impl PlaylistView {
|
||||||
@@ -31,12 +32,13 @@ impl PlaylistView {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let spotify = queue.get_spotify();
|
let spotify = queue.get_spotify();
|
||||||
let list = ListView::new(Arc::new(RwLock::new(tracks)), queue, library);
|
let list = ListView::new(Arc::new(RwLock::new(tracks)), queue, library.clone());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
playlist,
|
playlist,
|
||||||
list,
|
list,
|
||||||
spotify,
|
spotify,
|
||||||
|
library,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,8 +62,11 @@ impl ViewExt for PlaylistView {
|
|||||||
};
|
};
|
||||||
let track = tracks.get(pos);
|
let track = tracks.get(pos);
|
||||||
if let Some(t) = track {
|
if let Some(t) = track {
|
||||||
self.playlist
|
self.playlist.delete_tracks(
|
||||||
.delete_tracks(&[(t.clone(), pos)], self.spotify.clone());
|
&[(t.clone(), pos)],
|
||||||
|
self.spotify.clone(),
|
||||||
|
self.library.clone(),
|
||||||
|
);
|
||||||
self.list.remove(pos);
|
self.list.remove(pos);
|
||||||
}
|
}
|
||||||
return Ok(CommandResult::Consumed(None));
|
return Ok(CommandResult::Consumed(None));
|
||||||
|
|||||||
Reference in New Issue
Block a user