use crate::album::Album; use crate::artist::Artist; use crate::command::Command; use crate::commands::CommandResult; use crate::episode::Episode; use crate::events::EventManager; use crate::library::Library; use crate::playlist::Playlist; use crate::queue::Queue; use crate::show::Show; use crate::spotify::{Spotify, URIType}; use crate::spotify_url::SpotifyURL; use crate::track::Track; use crate::traits::{ListItem, ViewExt}; use crate::ui::listview::{ListView, Pagination}; use crate::ui::tabview::TabView; use cursive::view::ViewWrapper; use cursive::Cursive; use rspotify::model::search::SearchResult; use rspotify::senum::SearchType; use std::sync::{Arc, RwLock}; pub struct SearchResultsView { search_term: String, results_tracks: Arc>>, pagination_tracks: Pagination, results_albums: Arc>>, pagination_albums: Pagination, results_artists: Arc>>, pagination_artists: Pagination, results_playlists: Arc>>, pagination_playlists: Pagination, results_shows: Arc>>, pagination_shows: Pagination, results_episodes: Arc>>, pagination_episodes: Pagination, tabs: TabView, spotify: Arc, events: EventManager, } type SearchHandler = Box, &Arc>>, &str, usize, bool) -> u32 + Send + Sync>; impl SearchResultsView { pub fn new( search_term: String, events: EventManager, queue: Arc, library: Arc, ) -> SearchResultsView { let results_tracks = Arc::new(RwLock::new(Vec::new())); let results_albums = Arc::new(RwLock::new(Vec::new())); let results_artists = Arc::new(RwLock::new(Vec::new())); let results_playlists = Arc::new(RwLock::new(Vec::new())); let results_shows = Arc::new(RwLock::new(Vec::new())); let results_episodes = Arc::new(RwLock::new(Vec::new())); let list_tracks = ListView::new(results_tracks.clone(), queue.clone(), library.clone()); let pagination_tracks = list_tracks.get_pagination().clone(); let list_albums = ListView::new(results_albums.clone(), queue.clone(), library.clone()); let pagination_albums = list_albums.get_pagination().clone(); let list_artists = ListView::new(results_artists.clone(), queue.clone(), library.clone()); let pagination_artists = list_artists.get_pagination().clone(); let list_playlists = ListView::new(results_playlists.clone(), queue.clone(), library.clone()); let pagination_playlists = list_playlists.get_pagination().clone(); let list_shows = ListView::new(results_shows.clone(), queue.clone(), library.clone()); let pagination_shows = list_shows.get_pagination().clone(); let list_episodes = ListView::new(results_episodes.clone(), queue.clone(), library); let pagination_episodes = list_episodes.get_pagination().clone(); let tabs = TabView::new() .tab("tracks", "Tracks", list_tracks) .tab("albums", "Albums", list_albums) .tab("artists", "Artists", list_artists) .tab("playlists", "Playlists", list_playlists) .tab("shows", "Podcasts", list_shows) .tab("episodes", "Podcast Episodes", list_episodes); let mut view = SearchResultsView { search_term, results_tracks, pagination_tracks, results_albums, pagination_albums, results_artists, pagination_artists, results_playlists, pagination_playlists, results_shows, pagination_shows, results_episodes, pagination_episodes, tabs, spotify: queue.get_spotify(), events, }; view.run_search(); view } fn get_track( spotify: &Arc, tracks: &Arc>>, query: &str, _offset: usize, _append: bool, ) -> u32 { if let Some(results) = spotify.track(&query) { let t = vec![(&results).into()]; let mut r = tracks.write().unwrap(); *r = t; return 1; } 0 } fn search_track( spotify: &Arc, tracks: &Arc>>, query: &str, offset: usize, append: bool, ) -> u32 { if let Some(SearchResult::Tracks(results)) = spotify.search(SearchType::Track, &query, 50, offset as u32) { let mut t = results.items.iter().map(|ft| ft.into()).collect(); let mut r = tracks.write().unwrap(); if append { r.append(&mut t); } else { *r = t; } return results.total; } 0 } fn get_album( spotify: &Arc, albums: &Arc>>, query: &str, _offset: usize, _append: bool, ) -> u32 { if let Some(results) = spotify.album(&query) { let a = vec![(&results).into()]; let mut r = albums.write().unwrap(); *r = a; return 1; } 0 } fn search_album( spotify: &Arc, albums: &Arc>>, query: &str, offset: usize, append: bool, ) -> u32 { if let Some(SearchResult::Albums(results)) = spotify.search(SearchType::Album, &query, 50, offset as u32) { let mut a = results.items.iter().map(|sa| sa.into()).collect(); let mut r = albums.write().unwrap(); if append { r.append(&mut a); } else { *r = a; } return results.total; } 0 } fn get_artist( spotify: &Arc, artists: &Arc>>, query: &str, _offset: usize, _append: bool, ) -> u32 { if let Some(results) = spotify.artist(&query) { let a = vec![(&results).into()]; let mut r = artists.write().unwrap(); *r = a; return 1; } 0 } fn search_artist( spotify: &Arc, artists: &Arc>>, query: &str, offset: usize, append: bool, ) -> u32 { if let Some(SearchResult::Artists(results)) = spotify.search(SearchType::Artist, &query, 50, offset as u32) { let mut a = results.items.iter().map(|fa| fa.into()).collect(); let mut r = artists.write().unwrap(); if append { r.append(&mut a); } else { *r = a; } return results.total; } 0 } fn get_playlist( spotify: &Arc, playlists: &Arc>>, query: &str, _offset: usize, _append: bool, ) -> u32 { if let Some(result) = spotify.playlist(&query).as_ref() { let pls = vec![result.into()]; let mut r = playlists.write().unwrap(); *r = pls; return 1; } 0 } fn search_playlist( spotify: &Arc, playlists: &Arc>>, query: &str, offset: usize, append: bool, ) -> u32 { if let Some(SearchResult::Playlists(results)) = spotify.search(SearchType::Playlist, &query, 50, offset as u32) { let mut pls = results.items.iter().map(|sp| sp.into()).collect(); let mut r = playlists.write().unwrap(); if append { r.append(&mut pls); } else { *r = pls; } return results.total; } 0 } fn get_show( spotify: &Arc, shows: &Arc>>, query: &str, _offset: usize, _append: bool, ) -> u32 { if let Some(result) = spotify.get_show(&query).as_ref() { let pls = vec![result.into()]; let mut r = shows.write().unwrap(); *r = pls; return 1; } 0 } fn search_show( spotify: &Arc, shows: &Arc>>, query: &str, offset: usize, append: bool, ) -> u32 { if let Some(SearchResult::Shows(results)) = spotify.search(SearchType::Show, &query, 50, offset as u32) { let mut pls = results.items.iter().map(|sp| sp.into()).collect(); let mut r = shows.write().unwrap(); if append { r.append(&mut pls); } else { *r = pls; } return results.total; } 0 } fn get_episode( spotify: &Arc, episodes: &Arc>>, query: &str, _offset: usize, _append: bool, ) -> u32 { if let Some(result) = spotify.episode(&query).as_ref() { let e = vec![result.into()]; let mut r = episodes.write().unwrap(); *r = e; return 1; } 0 } fn search_episode( spotify: &Arc, episodes: &Arc>>, query: &str, offset: usize, append: bool, ) -> u32 { if let Some(SearchResult::Episodes(results)) = spotify.search(SearchType::Episode, &query, 50, offset as u32) { let mut e = results.items.iter().map(|se| se.into()).collect(); let mut r = episodes.write().unwrap(); if append { r.append(&mut e); } else { *r = e; } return results.total; } 0 } fn perform_search( &self, handler: SearchHandler, results: &Arc>>, query: &str, paginator: Option<&Pagination>, ) { let spotify = self.spotify.clone(); let query = query.to_owned(); let results = results.clone(); let ev = self.events.clone(); let paginator = paginator.cloned(); std::thread::spawn(move || { let total_items = handler(&spotify, &results, &query, 0, false) as usize; // register paginator if the API has more than one page of results if let Some(mut paginator) = paginator { if total_items > results.read().unwrap().len() { let ev = ev.clone(); // paginator callback let cb = move |items: Arc>>| { let offset = items.read().unwrap().len(); handler(&spotify, &results, &query, offset, true); ev.trigger(); }; paginator.set(total_items, Box::new(cb)); } else { paginator.clear() } } ev.trigger(); }); } pub fn run_search(&mut self) { let query = self.search_term.clone(); // check if API token refresh is necessary before commencing multiple // requests to avoid deadlock, as the parallel requests might // simultaneously try to refresh the token self.spotify.refresh_token(); // is the query a Spotify URI? if let Some(uritype) = URIType::from_uri(&query) { match uritype { URIType::Track => { self.perform_search( Box::new(Self::get_track), &self.results_tracks, &query, None, ); self.tabs.move_focus_to(0); } URIType::Album => { self.perform_search( Box::new(Self::get_album), &self.results_albums, &query, None, ); self.tabs.move_focus_to(1); } URIType::Artist => { self.perform_search( Box::new(Self::get_artist), &self.results_artists, &query, None, ); self.tabs.move_focus_to(2); } URIType::Playlist => { self.perform_search( Box::new(Self::get_playlist), &self.results_playlists, &query, None, ); self.tabs.move_focus_to(3); } URIType::Show => { self.perform_search( Box::new(Self::get_show), &self.results_shows, &query, None, ); self.tabs.move_focus_to(4); } URIType::Episode => { self.perform_search( Box::new(Self::get_episode), &self.results_episodes, &query, None, ); self.tabs.move_focus_to(5); } } // Is the query a spotify URL? // https://open.spotify.com/track/4uLU6hMCjMI75M1A2tKUQC } else if let Some(url) = SpotifyURL::from_url(&query) { match url.uri_type { URIType::Track => { self.perform_search( Box::new(Self::get_track), &self.results_tracks, &url.id, None, ); self.tabs.move_focus_to(0); } URIType::Album => { self.perform_search( Box::new(Self::get_album), &self.results_albums, &url.id, None, ); self.tabs.move_focus_to(1); } URIType::Artist => { self.perform_search( Box::new(Self::get_artist), &self.results_artists, &url.id, None, ); self.tabs.move_focus_to(2); } URIType::Playlist => { self.perform_search( Box::new(Self::get_playlist), &self.results_playlists, &url.id, None, ); self.tabs.move_focus_to(3); } URIType::Show => { self.perform_search( Box::new(Self::get_show), &self.results_shows, &url.id, None, ); self.tabs.move_focus_to(4); } URIType::Episode => { self.perform_search( Box::new(Self::get_episode), &self.results_episodes, &url.id, None, ); self.tabs.move_focus_to(5); } } } else { self.perform_search( Box::new(Self::search_track), &self.results_tracks, &query, Some(&self.pagination_tracks), ); self.perform_search( Box::new(Self::search_album), &self.results_albums, &query, Some(&self.pagination_albums), ); self.perform_search( Box::new(Self::search_artist), &self.results_artists, &query, Some(&self.pagination_artists), ); self.perform_search( Box::new(Self::search_playlist), &self.results_playlists, &query, Some(&self.pagination_playlists), ); self.perform_search( Box::new(Self::search_show), &self.results_shows, &query, Some(&self.pagination_shows), ); self.perform_search( Box::new(Self::search_episode), &self.results_episodes, &query, Some(&self.pagination_episodes), ); } } } impl ViewWrapper for SearchResultsView { wrap_impl!(self.tabs: TabView); } impl ViewExt for SearchResultsView { fn title(&self) -> String { format!("Search: {}", self.search_term) } fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result { self.tabs.on_command(s, cmd) } }