@@ -3,14 +3,16 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
use crate::events::EventManager;
|
||||||
use crate::library::Library;
|
use crate::library::Library;
|
||||||
use crate::queue::{Queue, RepeatSetting};
|
use crate::queue::{Queue, RepeatSetting};
|
||||||
use crate::spotify::{Spotify, VOLUME_PERCENT};
|
use crate::spotify::{Spotify, VOLUME_PERCENT};
|
||||||
use crate::traits::ViewExt;
|
use crate::traits::{ViewExt, IntoBoxedViewExt};
|
||||||
use crate::ui::contextmenu::ContextMenu;
|
use crate::ui::contextmenu::ContextMenu;
|
||||||
use crate::ui::help::HelpView;
|
use crate::ui::help::HelpView;
|
||||||
use crate::ui::layout::Layout;
|
use crate::ui::layout::Layout;
|
||||||
use crate::ui::modal::Modal;
|
use crate::ui::modal::Modal;
|
||||||
|
use crate::ui::search_results::SearchResultsView;
|
||||||
use crate::UserData;
|
use crate::UserData;
|
||||||
use crate::{
|
use crate::{
|
||||||
command::{
|
command::{
|
||||||
@@ -39,6 +41,7 @@ pub struct CommandManager {
|
|||||||
queue: Arc<Queue>,
|
queue: Arc<Queue>,
|
||||||
library: Arc<Library>,
|
library: Arc<Library>,
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
|
events: EventManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandManager {
|
impl CommandManager {
|
||||||
@@ -47,6 +50,7 @@ impl CommandManager {
|
|||||||
queue: Arc<Queue>,
|
queue: Arc<Queue>,
|
||||||
library: Arc<Library>,
|
library: Arc<Library>,
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
|
events: EventManager,
|
||||||
) -> CommandManager {
|
) -> CommandManager {
|
||||||
let bindings = RefCell::new(Self::get_bindings(config.clone()));
|
let bindings = RefCell::new(Self::get_bindings(config.clone()));
|
||||||
CommandManager {
|
CommandManager {
|
||||||
@@ -56,6 +60,7 @@ impl CommandManager {
|
|||||||
queue,
|
queue,
|
||||||
library,
|
library,
|
||||||
config,
|
config,
|
||||||
|
events,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +209,16 @@ impl CommandManager {
|
|||||||
}
|
}
|
||||||
Ok(None)
|
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::Jump(_)
|
||||||
| Command::Move(_, _)
|
| Command::Move(_, _)
|
||||||
| Command::Shift(_, _)
|
| Command::Shift(_, _)
|
||||||
|
|||||||
16
src/main.rs
16
src/main.rs
@@ -237,8 +237,13 @@ fn main() {
|
|||||||
|
|
||||||
let library = Arc::new(Library::new(&event_manager, spotify.clone(), cfg.clone()));
|
let library = Arc::new(Library::new(&event_manager, spotify.clone(), cfg.clone()));
|
||||||
|
|
||||||
let mut cmd_manager =
|
let mut cmd_manager = CommandManager::new(
|
||||||
CommandManager::new(spotify.clone(), queue.clone(), library.clone(), cfg.clone());
|
spotify.clone(),
|
||||||
|
queue.clone(),
|
||||||
|
library.clone(),
|
||||||
|
cfg.clone(),
|
||||||
|
event_manager.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
cmd_manager.register_all();
|
cmd_manager.register_all();
|
||||||
cmd_manager.register_keybindings(&mut cursive);
|
cmd_manager.register_keybindings(&mut cursive);
|
||||||
@@ -246,12 +251,7 @@ fn main() {
|
|||||||
let user_data: UserData = Arc::new(UserDataInner { cmd: cmd_manager });
|
let user_data: UserData = Arc::new(UserDataInner { cmd: cmd_manager });
|
||||||
cursive.set_user_data(user_data);
|
cursive.set_user_data(user_data);
|
||||||
|
|
||||||
let search = ui::search::SearchView::new(
|
let search = ui::search::SearchView::new(event_manager.clone(), queue.clone(), library.clone());
|
||||||
event_manager.clone(),
|
|
||||||
spotify.clone(),
|
|
||||||
queue.clone(),
|
|
||||||
library.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let libraryview = ui::library::LibraryView::new(queue.clone(), library.clone());
|
let libraryview = ui::library::LibraryView::new(queue.clone(), library.clone());
|
||||||
|
|
||||||
|
|||||||
@@ -292,12 +292,6 @@ impl View for Layout {
|
|||||||
impl ViewExt for Layout {
|
impl ViewExt for Layout {
|
||||||
fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result<CommandResult, String> {
|
fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result<CommandResult, String> {
|
||||||
match cmd {
|
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) => {
|
Command::Focus(view) => {
|
||||||
if self.views.keys().any(|k| k == view) {
|
if self.views.keys().any(|k| k == view) {
|
||||||
self.set_view(view.clone());
|
self.set_view(view.clone());
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ pub mod playlist;
|
|||||||
pub mod playlists;
|
pub mod playlists;
|
||||||
pub mod queue;
|
pub mod queue;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
|
pub mod search_results;
|
||||||
pub mod show;
|
pub mod show;
|
||||||
pub mod statusbar;
|
pub mod statusbar;
|
||||||
pub mod tabview;
|
pub mod tabview;
|
||||||
|
|||||||
547
src/ui/search.rs
547
src/ui/search.rs
@@ -3,7 +3,7 @@
|
|||||||
use cursive::direction::Orientation;
|
use cursive::direction::Orientation;
|
||||||
use cursive::event::{AnyCb, Event, EventResult, Key};
|
use cursive::event::{AnyCb, Event, EventResult, Key};
|
||||||
use cursive::traits::{Boxable, Finder, Identifiable, View};
|
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::views::{EditView, NamedView, ViewRef};
|
||||||
use cursive::{Cursive, Printer, Vec2};
|
use cursive::{Cursive, Printer, Vec2};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
@@ -22,101 +22,39 @@ use crate::show::Show;
|
|||||||
use crate::spotify::{Spotify, URIType};
|
use crate::spotify::{Spotify, URIType};
|
||||||
use crate::track::Track;
|
use crate::track::Track;
|
||||||
use crate::traits::{ListItem, ViewExt};
|
use crate::traits::{ListItem, ViewExt};
|
||||||
|
use crate::ui::layout::Layout;
|
||||||
use crate::ui::listview::{ListView, Pagination};
|
use crate::ui::listview::{ListView, Pagination};
|
||||||
|
use crate::ui::search_results::SearchResultsView;
|
||||||
use crate::ui::tabview::TabView;
|
use crate::ui::tabview::TabView;
|
||||||
use rspotify::model::search::SearchResult;
|
use rspotify::model::search::SearchResult;
|
||||||
use rspotify::senum::SearchType;
|
use rspotify::senum::SearchType;
|
||||||
|
|
||||||
pub struct SearchView {
|
pub struct SearchView {
|
||||||
results_tracks: Arc<RwLock<Vec<Track>>>,
|
|
||||||
pagination_tracks: Pagination<Track>,
|
|
||||||
results_albums: Arc<RwLock<Vec<Album>>>,
|
|
||||||
pagination_albums: Pagination<Album>,
|
|
||||||
results_artists: Arc<RwLock<Vec<Artist>>>,
|
|
||||||
pagination_artists: Pagination<Artist>,
|
|
||||||
results_playlists: Arc<RwLock<Vec<Playlist>>>,
|
|
||||||
pagination_playlists: Pagination<Playlist>,
|
|
||||||
results_shows: Arc<RwLock<Vec<Show>>>,
|
|
||||||
pagination_shows: Pagination<Show>,
|
|
||||||
results_episodes: Arc<RwLock<Vec<Episode>>>,
|
|
||||||
pagination_episodes: Pagination<Episode>,
|
|
||||||
edit: NamedView<EditView>,
|
edit: NamedView<EditView>,
|
||||||
tabs: NamedView<TabView>,
|
|
||||||
edit_focused: bool,
|
edit_focused: bool,
|
||||||
events: EventManager,
|
|
||||||
spotify: Arc<Spotify>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SearchHandler<I> =
|
|
||||||
Box<dyn Fn(&Arc<Spotify>, &Arc<RwLock<Vec<I>>>, &str, usize, bool) -> u32 + Send + Sync>;
|
|
||||||
|
|
||||||
pub const LIST_ID: &str = "search_list";
|
|
||||||
pub const EDIT_ID: &str = "search_edit";
|
pub const EDIT_ID: &str = "search_edit";
|
||||||
impl SearchView {
|
|
||||||
pub fn new(
|
|
||||||
events: EventManager,
|
|
||||||
spotify: Arc<Spotify>,
|
|
||||||
queue: Arc<Queue>,
|
|
||||||
library: Arc<Library>,
|
|
||||||
) -> 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<Queue>, library: Arc<Library>) -> SearchView {
|
||||||
let searchfield = EditView::new()
|
let searchfield = EditView::new()
|
||||||
.on_submit(move |s, input| {
|
.on_submit(move |s, input| {
|
||||||
if !input.is_empty() {
|
if !input.is_empty() {
|
||||||
s.call_on_name("search", |v: &mut SearchView| {
|
let results = SearchResultsView::new(
|
||||||
v.run_search(input);
|
input.to_string(),
|
||||||
v.focus_view(&Selector::Name(LIST_ID)).unwrap();
|
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);
|
.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 {
|
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,
|
edit: searchfield,
|
||||||
tabs: tabs.with_name(LIST_ID),
|
|
||||||
edit_focused: true,
|
edit_focused: true,
|
||||||
events,
|
|
||||||
spotify,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,431 +64,19 @@ impl SearchView {
|
|||||||
v.set_content("");
|
v.set_content("");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_track(
|
|
||||||
spotify: &Arc<Spotify>,
|
|
||||||
tracks: &Arc<RwLock<Vec<Track>>>,
|
|
||||||
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<Spotify>,
|
|
||||||
tracks: &Arc<RwLock<Vec<Track>>>,
|
|
||||||
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<Spotify>,
|
|
||||||
albums: &Arc<RwLock<Vec<Album>>>,
|
|
||||||
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<Spotify>,
|
|
||||||
albums: &Arc<RwLock<Vec<Album>>>,
|
|
||||||
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<Spotify>,
|
|
||||||
artists: &Arc<RwLock<Vec<Artist>>>,
|
|
||||||
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<Spotify>,
|
|
||||||
artists: &Arc<RwLock<Vec<Artist>>>,
|
|
||||||
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<Spotify>,
|
|
||||||
playlists: &Arc<RwLock<Vec<Playlist>>>,
|
|
||||||
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<Spotify>,
|
|
||||||
playlists: &Arc<RwLock<Vec<Playlist>>>,
|
|
||||||
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<Spotify>,
|
|
||||||
shows: &Arc<RwLock<Vec<Show>>>,
|
|
||||||
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<Spotify>,
|
|
||||||
shows: &Arc<RwLock<Vec<Show>>>,
|
|
||||||
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<Spotify>,
|
|
||||||
episodes: &Arc<RwLock<Vec<Episode>>>,
|
|
||||||
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<Spotify>,
|
|
||||||
episodes: &Arc<RwLock<Vec<Episode>>>,
|
|
||||||
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<I: ListItem>(
|
|
||||||
&self,
|
|
||||||
handler: SearchHandler<I>,
|
|
||||||
results: &Arc<RwLock<Vec<I>>>,
|
|
||||||
query: &str,
|
|
||||||
paginator: Option<&Pagination<I>>,
|
|
||||||
) {
|
|
||||||
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<RwLock<Vec<I>>>| {
|
|
||||||
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<S: Into<String>>(&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 {
|
impl View for SearchView {
|
||||||
fn draw(&self, printer: &Printer<'_, '_>) {
|
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
|
let printer = &printer
|
||||||
.offset((0, 1))
|
.offset((0, 0))
|
||||||
.cropped((printer.size.x, printer.size.y - 1))
|
.cropped((printer.size.x, 1))
|
||||||
.focused(!self.edit_focused);
|
.focused(self.edit_focused);
|
||||||
self.tabs.draw(printer);
|
self.edit.draw(printer);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&mut self, size: Vec2) {
|
fn layout(&mut self, size: Vec2) {
|
||||||
self.edit.layout(Vec2::new(size.x, 1));
|
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 {
|
fn on_event(&mut self, event: Event) -> EventResult {
|
||||||
@@ -562,13 +88,12 @@ impl View for SearchView {
|
|||||||
if self.edit_focused {
|
if self.edit_focused {
|
||||||
self.edit.on_event(event)
|
self.edit.on_event(event)
|
||||||
} else {
|
} else {
|
||||||
self.tabs.on_event(event)
|
EventResult::Ignored
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call_on_any<'a>(&mut self, selector: &Selector<'_>, callback: AnyCb<'a>) {
|
fn call_on_any<'a>(&mut self, selector: &Selector<'_>, callback: AnyCb<'a>) {
|
||||||
self.edit.call_on_any(selector, &mut |v| callback(v));
|
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<(), ()> {
|
fn focus_view(&mut self, selector: &Selector<'_>) -> Result<(), ()> {
|
||||||
@@ -582,39 +107,13 @@ impl View for SearchView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ViewExt for SearchView {
|
impl ViewExt for SearchView {
|
||||||
fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result<CommandResult, String> {
|
fn on_command(&mut self, _s: &mut Cursive, cmd: &Command) -> Result<CommandResult, String> {
|
||||||
match cmd {
|
if let Command::Focus(_) = cmd {
|
||||||
Command::Search(query) => self.run_search(query.to_string()),
|
self.edit_focused = true;
|
||||||
Command::Focus(_) => {
|
self.clear();
|
||||||
self.edit_focused = true;
|
return Ok(CommandResult::Consumed(None));
|
||||||
self.clear();
|
|
||||||
return Ok(CommandResult::Consumed(None));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = if !self.edit_focused {
|
Ok(CommandResult::Ignored)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
507
src/ui/search_results.rs
Normal file
507
src/ui/search_results.rs
Normal file
@@ -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<RwLock<Vec<Track>>>,
|
||||||
|
pagination_tracks: Pagination<Track>,
|
||||||
|
results_albums: Arc<RwLock<Vec<Album>>>,
|
||||||
|
pagination_albums: Pagination<Album>,
|
||||||
|
results_artists: Arc<RwLock<Vec<Artist>>>,
|
||||||
|
pagination_artists: Pagination<Artist>,
|
||||||
|
results_playlists: Arc<RwLock<Vec<Playlist>>>,
|
||||||
|
pagination_playlists: Pagination<Playlist>,
|
||||||
|
results_shows: Arc<RwLock<Vec<Show>>>,
|
||||||
|
pagination_shows: Pagination<Show>,
|
||||||
|
results_episodes: Arc<RwLock<Vec<Episode>>>,
|
||||||
|
pagination_episodes: Pagination<Episode>,
|
||||||
|
tabs: TabView,
|
||||||
|
spotify: Arc<Spotify>,
|
||||||
|
events: EventManager,
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchHandler<I> =
|
||||||
|
Box<dyn Fn(&Arc<Spotify>, &Arc<RwLock<Vec<I>>>, &str, usize, bool) -> u32 + Send + Sync>;
|
||||||
|
|
||||||
|
impl SearchResultsView {
|
||||||
|
pub fn new(
|
||||||
|
search_term: String,
|
||||||
|
events: EventManager,
|
||||||
|
queue: Arc<Queue>,
|
||||||
|
library: Arc<Library>,
|
||||||
|
) -> 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<Spotify>,
|
||||||
|
tracks: &Arc<RwLock<Vec<Track>>>,
|
||||||
|
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<Spotify>,
|
||||||
|
tracks: &Arc<RwLock<Vec<Track>>>,
|
||||||
|
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<Spotify>,
|
||||||
|
albums: &Arc<RwLock<Vec<Album>>>,
|
||||||
|
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<Spotify>,
|
||||||
|
albums: &Arc<RwLock<Vec<Album>>>,
|
||||||
|
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<Spotify>,
|
||||||
|
artists: &Arc<RwLock<Vec<Artist>>>,
|
||||||
|
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<Spotify>,
|
||||||
|
artists: &Arc<RwLock<Vec<Artist>>>,
|
||||||
|
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<Spotify>,
|
||||||
|
playlists: &Arc<RwLock<Vec<Playlist>>>,
|
||||||
|
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<Spotify>,
|
||||||
|
playlists: &Arc<RwLock<Vec<Playlist>>>,
|
||||||
|
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<Spotify>,
|
||||||
|
shows: &Arc<RwLock<Vec<Show>>>,
|
||||||
|
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<Spotify>,
|
||||||
|
shows: &Arc<RwLock<Vec<Show>>>,
|
||||||
|
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<Spotify>,
|
||||||
|
episodes: &Arc<RwLock<Vec<Episode>>>,
|
||||||
|
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<Spotify>,
|
||||||
|
episodes: &Arc<RwLock<Vec<Episode>>>,
|
||||||
|
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<I: ListItem>(
|
||||||
|
&self,
|
||||||
|
handler: SearchHandler<I>,
|
||||||
|
results: &Arc<RwLock<Vec<I>>>,
|
||||||
|
query: &str,
|
||||||
|
paginator: Option<&Pagination<I>>,
|
||||||
|
) {
|
||||||
|
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<RwLock<Vec<I>>>| {
|
||||||
|
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<CommandResult, String> {
|
||||||
|
self.tabs.on_command(s, cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user