use std::iter::Iterator; use std::ops::Deref; use std::sync::{Arc, RwLock}; use rspotify::spotify::model::playlist::SimplifiedPlaylist; use events::{Event, EventManager}; use spotify::Spotify; use track::Track; #[derive(Clone, Deserialize, Serialize)] pub struct Playlist { pub meta: SimplifiedPlaylist, pub tracks: Vec, } pub enum PlaylistEvent { NewList(usize, Playlist), } #[derive(Default, Serialize, Deserialize)] pub struct PlaylistStore { pub playlists: Vec, } #[derive(Clone)] pub struct Playlists { pub store: Arc>, ev: EventManager, spotify: Arc, } impl Playlists { pub fn new(ev: &EventManager, spotify: &Arc) -> Playlists { Playlists { store: Arc::new(RwLock::new(PlaylistStore::default())), ev: ev.clone(), spotify: spotify.clone(), } } pub fn load_cache(&self) { let xdg_dirs = xdg::BaseDirectories::with_prefix("ncspot").unwrap(); if let Ok(cache_path) = xdg_dirs.place_cache_file("playlists.db") { if let Ok(contents) = std::fs::read_to_string(&cache_path) { debug!( "loading playlist cache from {}", cache_path.to_str().unwrap() ); let parsed: Result = serde_json::from_str(&contents); if let Ok(cache) = parsed { debug!("playlist cache loaded ({} lists)", cache.playlists.len()); let mut store = self.store.write().expect("can't writelock playlist store"); store.playlists.clear(); store.playlists.extend(cache.playlists); // force refresh of UI (if visible) self.ev.send(Event::ScreenChange("playlists".to_owned())); } else { error!("playlist cache corrupted?"); } } } } pub fn save_cache(&self) { let xdg_dirs = xdg::BaseDirectories::with_prefix("ncspot").unwrap(); if let Ok(cache_path) = xdg_dirs.place_cache_file("playlists.db") { match serde_json::to_string(&self.store.deref()) { Ok(contents) => std::fs::write(cache_path, contents).unwrap(), Err(e) => error!("could not write playlist cache: {:?}", e), } } } fn process_playlist(list: &SimplifiedPlaylist, spotify: &Spotify) -> Playlist { debug!("got list: {}", list.name); let id = list.id.clone(); let mut collected_tracks = Vec::new(); let mut tracks_result = spotify.user_playlist_tracks(&id, 100, 0).ok(); while let Some(ref tracks) = tracks_result.clone() { for listtrack in &tracks.items { collected_tracks.push(Track::new(&listtrack.track)); } debug!("got {} tracks", tracks.items.len()); // load next batch if necessary tracks_result = match tracks.next { Some(_) => { debug!("requesting tracks again.."); spotify .user_playlist_tracks(&id, 100, tracks.offset + tracks.items.len() as u32) .ok() } None => None, } } Playlist { meta: list.clone(), tracks: collected_tracks, } } fn needs_download(&self, remote: &SimplifiedPlaylist) -> bool { for local in &self .store .read() .expect("can't readlock playlists") .playlists { if local.meta.id == remote.id { return local.meta.snapshot_id != remote.snapshot_id; } } true } fn append_or_update(&self, updated: &Playlist) -> usize { let mut store = self.store.write().expect("can't writelock playlists"); for (index, mut local) in store.playlists.iter_mut().enumerate() { if local.meta.id == updated.meta.id { *local = updated.clone(); return index; } } store.playlists.push(updated.clone()); store.playlists.len() - 1 } pub fn fetch_playlists(&self) { debug!("loading playlists"); let mut lists_result = self.spotify.current_user_playlist(50, 0).ok(); while let Some(ref lists) = lists_result.clone() { for remote in &lists.items { if self.needs_download(remote) { info!("updating playlist {}", remote.name); let playlist = Self::process_playlist(&remote, &self.spotify); let index = self.append_or_update(&playlist); self.ev.send(Event::Playlist(PlaylistEvent::NewList( index, playlist.clone(), ))); } } // load next batch if necessary lists_result = match lists.next { Some(_) => { debug!("requesting playlists again.."); self.spotify .current_user_playlist(50, lists.offset + lists.items.len() as u32) .ok() } None => None, } } } }