diff --git a/src/commands.rs b/src/commands.rs index 1a10d95..cecb3e8 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -3,14 +3,16 @@ use std::sync::Arc; use std::time::Duration; use crate::config::Config; +use crate::events::EventManager; use crate::library::Library; use crate::queue::{Queue, RepeatSetting}; use crate::spotify::{Spotify, VOLUME_PERCENT}; -use crate::traits::ViewExt; +use crate::traits::{ViewExt, IntoBoxedViewExt}; use crate::ui::contextmenu::ContextMenu; use crate::ui::help::HelpView; use crate::ui::layout::Layout; use crate::ui::modal::Modal; +use crate::ui::search_results::SearchResultsView; use crate::UserData; use crate::{ command::{ @@ -39,6 +41,7 @@ pub struct CommandManager { queue: Arc, library: Arc, config: Arc, + events: EventManager, } impl CommandManager { @@ -47,6 +50,7 @@ impl CommandManager { queue: Arc, library: Arc, config: Arc, + events: EventManager, ) -> CommandManager { let bindings = RefCell::new(Self::get_bindings(config.clone())); CommandManager { @@ -56,6 +60,7 @@ impl CommandManager { queue, library, config, + events, } } @@ -204,6 +209,16 @@ impl CommandManager { } Ok(None) } + Command::Search(term) => { + let view = SearchResultsView::new( + term.clone(), + self.events.clone(), + self.queue.clone(), + self.library.clone(), + ); + s.call_on_name("main", |v: &mut Layout| v.push_view(view.as_boxed_view_ext())); + Ok(None) + } Command::Jump(_) | Command::Move(_, _) | Command::Shift(_, _) diff --git a/src/main.rs b/src/main.rs index 818c7f2..e7a93a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -237,8 +237,13 @@ fn main() { let library = Arc::new(Library::new(&event_manager, spotify.clone(), cfg.clone())); - let mut cmd_manager = - CommandManager::new(spotify.clone(), queue.clone(), library.clone(), cfg.clone()); + let mut cmd_manager = CommandManager::new( + spotify.clone(), + queue.clone(), + library.clone(), + cfg.clone(), + event_manager.clone(), + ); cmd_manager.register_all(); cmd_manager.register_keybindings(&mut cursive); @@ -246,12 +251,7 @@ fn main() { let user_data: UserData = Arc::new(UserDataInner { cmd: cmd_manager }); cursive.set_user_data(user_data); - let search = ui::search::SearchView::new( - event_manager.clone(), - spotify.clone(), - queue.clone(), - library.clone(), - ); + let search = ui::search::SearchView::new(event_manager.clone(), queue.clone(), library.clone()); let libraryview = ui::library::LibraryView::new(queue.clone(), library.clone()); diff --git a/src/ui/layout.rs b/src/ui/layout.rs index 9af14da..a937352 100644 --- a/src/ui/layout.rs +++ b/src/ui/layout.rs @@ -292,12 +292,6 @@ impl View for Layout { impl ViewExt for Layout { fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result { match cmd { - Command::Search(_) => { - self.set_view("search"); - self.get_current_screen_mut() - .map(|search| search.view.on_command(s, cmd)); - Ok(CommandResult::Consumed(None)) - } Command::Focus(view) => { if self.views.keys().any(|k| k == view) { self.set_view(view.clone()); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index b455fc8..e6500a4 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -10,6 +10,7 @@ pub mod playlist; pub mod playlists; pub mod queue; pub mod search; +pub mod search_results; pub mod show; pub mod statusbar; pub mod tabview; diff --git a/src/ui/search.rs b/src/ui/search.rs index d1e3c29..804b17f 100644 --- a/src/ui/search.rs +++ b/src/ui/search.rs @@ -3,7 +3,7 @@ use cursive::direction::Orientation; use cursive::event::{AnyCb, Event, EventResult, Key}; use cursive::traits::{Boxable, Finder, Identifiable, View}; -use cursive::view::{Selector, ViewWrapper}; +use cursive::view::{IntoBoxedView, Selector, ViewWrapper}; use cursive::views::{EditView, NamedView, ViewRef}; use cursive::{Cursive, Printer, Vec2}; use std::cell::RefCell; @@ -22,101 +22,39 @@ use crate::show::Show; use crate::spotify::{Spotify, URIType}; use crate::track::Track; use crate::traits::{ListItem, ViewExt}; +use crate::ui::layout::Layout; use crate::ui::listview::{ListView, Pagination}; +use crate::ui::search_results::SearchResultsView; use crate::ui::tabview::TabView; use rspotify::model::search::SearchResult; use rspotify::senum::SearchType; pub struct SearchView { - 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, edit: NamedView, - tabs: NamedView, edit_focused: bool, - events: EventManager, - spotify: Arc, } -type SearchHandler = - Box, &Arc>>, &str, usize, bool) -> u32 + Send + Sync>; - -pub const LIST_ID: &str = "search_list"; pub const EDIT_ID: &str = "search_edit"; -impl SearchView { - pub fn new( - events: EventManager, - spotify: Arc, - queue: Arc, - library: Arc, - ) -> SearchView { - 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())); +impl SearchView { + pub fn new(events: EventManager, queue: Arc, library: Arc) -> SearchView { let searchfield = EditView::new() .on_submit(move |s, input| { if !input.is_empty() { - s.call_on_name("search", |v: &mut SearchView| { - v.run_search(input); - v.focus_view(&Selector::Name(LIST_ID)).unwrap(); - }); + let results = SearchResultsView::new( + input.to_string(), + events.clone(), + queue.clone(), + library.clone(), + ); + s.call_on_name("main", move |v: &mut Layout| v.push_view(Box::new(results))); } }) .with_name(EDIT_ID); - 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, 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); - SearchView { - results_tracks, - pagination_tracks, - results_albums, - pagination_albums, - results_artists, - pagination_artists, - results_playlists, - pagination_playlists, - results_shows, - pagination_shows, - results_episodes, - pagination_episodes, edit: searchfield, - tabs: tabs.with_name(LIST_ID), edit_focused: true, - events, - spotify, } } @@ -126,431 +64,19 @@ impl SearchView { v.set_content(""); }); } - - 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, query: S) { - let query = query.into(); - - self.edit_focused = false; - - { - let query = query.clone(); - self.edit - .call_on(&Selector::Name(EDIT_ID), |v: &mut EditView| { - v.set_content(query); - }); - } - - // 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) { - // Clear the results if we are going to process a Spotify URI. We need - // to do this since we are only calling the search function for the - // given URI type which leaves the previous search results intact. - let results_tracks = self.results_tracks.clone(); - *results_tracks.write().unwrap() = Vec::new(); - let results_albums = self.results_albums.clone(); - *results_albums.write().unwrap() = Vec::new(); - let results_artists = self.results_artists.clone(); - *results_artists.write().unwrap() = Vec::new(); - let results_playlists = self.results_playlists.clone(); - *results_playlists.write().unwrap() = Vec::new(); - let results_shows = self.results_shows.clone(); - *results_shows.write().unwrap() = Vec::new(); - let results_episodes = self.results_episodes.clone(); - *results_episodes.write().unwrap() = Vec::new(); - - let mut tab_view = self.tabs.get_mut(); - match uritype { - URIType::Track => { - self.perform_search( - Box::new(Self::get_track), - &self.results_tracks, - &query, - None, - ); - tab_view.move_focus_to(0); - } - URIType::Album => { - self.perform_search( - Box::new(Self::get_album), - &self.results_albums, - &query, - None, - ); - tab_view.move_focus_to(1); - } - URIType::Artist => { - self.perform_search( - Box::new(Self::get_artist), - &self.results_artists, - &query, - None, - ); - tab_view.move_focus_to(2); - } - URIType::Playlist => { - self.perform_search( - Box::new(Self::get_playlist), - &self.results_playlists, - &query, - None, - ); - tab_view.move_focus_to(3); - } - URIType::Show => { - self.perform_search( - Box::new(Self::get_show), - &self.results_shows, - &query, - None, - ); - tab_view.move_focus_to(4); - } - URIType::Episode => { - self.perform_search( - Box::new(Self::get_episode), - &self.results_episodes, - &query, - None, - ); - tab_view.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 View for SearchView { fn draw(&self, printer: &Printer<'_, '_>) { - { - let printer = &printer - .offset((0, 0)) - .cropped((printer.size.x, 1)) - .focused(self.edit_focused); - self.edit.draw(printer); - } - let printer = &printer - .offset((0, 1)) - .cropped((printer.size.x, printer.size.y - 1)) - .focused(!self.edit_focused); - self.tabs.draw(printer); + .offset((0, 0)) + .cropped((printer.size.x, 1)) + .focused(self.edit_focused); + self.edit.draw(printer); } fn layout(&mut self, size: Vec2) { self.edit.layout(Vec2::new(size.x, 1)); - self.tabs.layout(Vec2::new(size.x, size.y - 1)); } fn on_event(&mut self, event: Event) -> EventResult { @@ -562,13 +88,12 @@ impl View for SearchView { if self.edit_focused { self.edit.on_event(event) } else { - self.tabs.on_event(event) + EventResult::Ignored } } fn call_on_any<'a>(&mut self, selector: &Selector<'_>, callback: AnyCb<'a>) { self.edit.call_on_any(selector, &mut |v| callback(v)); - self.tabs.call_on_any(selector, &mut |v| callback(v)); } fn focus_view(&mut self, selector: &Selector<'_>) -> Result<(), ()> { @@ -582,39 +107,13 @@ impl View for SearchView { } impl ViewExt for SearchView { - fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result { - match cmd { - Command::Search(query) => self.run_search(query.to_string()), - Command::Focus(_) => { - self.edit_focused = true; - self.clear(); - return Ok(CommandResult::Consumed(None)); - } - _ => {} + fn on_command(&mut self, _s: &mut Cursive, cmd: &Command) -> Result { + if let Command::Focus(_) = cmd { + self.edit_focused = true; + self.clear(); + return Ok(CommandResult::Consumed(None)); } - let result = if !self.edit_focused { - self.tabs.on_command(s, cmd)? - } else { - CommandResult::Ignored - }; - - if let CommandResult::Ignored = result { - if let Command::Move(mode, _) = cmd { - match mode { - MoveMode::Up if !self.edit_focused => { - self.edit_focused = true; - return Ok(CommandResult::Consumed(None)); - } - MoveMode::Down if self.edit_focused => { - self.edit_focused = false; - return Ok(CommandResult::Consumed(None)); - } - _ => {} - } - } - } - - Ok(result) + Ok(CommandResult::Ignored) } } diff --git a/src/ui/search_results.rs b/src/ui/search_results.rs new file mode 100644 index 0000000..a2373da --- /dev/null +++ b/src/ui/search_results.rs @@ -0,0 +1,507 @@ +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::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) { + // Clear the results if we are going to process a Spotify URI. We need + // to do this since we are only calling the search function for the + // given URI type which leaves the previous search results intact. + let results_tracks = self.results_tracks.clone(); + *results_tracks.write().unwrap() = Vec::new(); + let results_albums = self.results_albums.clone(); + *results_albums.write().unwrap() = Vec::new(); + let results_artists = self.results_artists.clone(); + *results_artists.write().unwrap() = Vec::new(); + let results_playlists = self.results_playlists.clone(); + *results_playlists.write().unwrap() = Vec::new(); + let results_shows = self.results_shows.clone(); + *results_shows.write().unwrap() = Vec::new(); + let results_episodes = self.results_episodes.clone(); + *results_episodes.write().unwrap() = Vec::new(); + + 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); + } + } + } 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) + } +}