diff --git a/Cargo.toml b/Cargo.toml index bae0c1c..e6331d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "movie_collection_rust" -version = "0.10.29" +version = "0.10.30" authors = ["Daniel Boline "] edition = "2018" diff --git a/movie_collection_http/Cargo.toml b/movie_collection_http/Cargo.toml index 3b03962..65cb5fc 100644 --- a/movie_collection_http/Cargo.toml +++ b/movie_collection_http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "movie_collection_http" -version = "0.10.29" +version = "0.10.30" authors = ["Daniel Boline "] edition = "2018" diff --git a/movie_collection_http/src/lib.rs b/movie_collection_http/src/lib.rs index 224d13d..6813b66 100644 --- a/movie_collection_http/src/lib.rs +++ b/movie_collection_http/src/lib.rs @@ -390,6 +390,30 @@ struct _PlexFilenameRequest { limit: Option, } +#[derive(Default, Clone, Debug, Serialize, Deserialize, Into, From)] +pub struct TraktWatchlistRequest { + pub query: Option, + pub source: Option, + pub offset: Option, + pub limit: Option, +} + +derive_rweb_schema!(TraktWatchlistRequest, _TraktWatchlistRequest); + +#[allow(dead_code)] +#[derive(Schema)] +#[schema(component = "TraktWatchlistRequest")] +pub struct _TraktWatchlistRequest { + #[schema(description = "Search Query")] + pub query: Option, + #[schema(description = "Tv Show Source")] + pub source: Option, + #[schema(description = "Offset")] + pub offset: Option, + #[schema(description = "Limit")] + pub limit: Option, +} + #[cfg(test)] mod test { use rweb_helper::derive_rweb_test; diff --git a/movie_collection_http/src/movie_queue_elements.rs b/movie_collection_http/src/movie_queue_elements.rs index 5aad843..a7f06f4 100644 --- a/movie_collection_http/src/movie_queue_elements.rs +++ b/movie_collection_http/src/movie_queue_elements.rs @@ -64,7 +64,7 @@ fn index_element(cx: Scope) -> Element { "type": "button", name: "tvshows", value: "TVShows", - "onclick": "updateMainArticle('/list/tvshows');", + "onclick": "updateMainArticle('/list/tvshows?offset=0&limit=10');", }, input { "type": "button", @@ -76,7 +76,7 @@ fn index_element(cx: Scope) -> Element { "type": "button", "name": "watchlist", value: "WatchList", - "onclick": "updateMainArticle('/trakt/watchlist');" + "onclick": "updateMainArticle('/trakt/watchlist?offset=0&limit=10');" }, input { "type": "button", @@ -88,13 +88,13 @@ fn index_element(cx: Scope) -> Element { "type": "button", "name": "list", value: "FullQueue", - "onclick": "updateMainArticle('/list/full_queue?limit=20');" + "onclick": "updateMainArticle('/list/full_queue?limit=10');" }, input { "type": "button", "name": "plex", value: "PlexList", - "onclick": "updateMainArticle('/list/plex?limit=20');" + "onclick": "updateMainArticle('/list/plex?limit=10');" }, input { "type": "button", @@ -225,7 +225,7 @@ fn MovieQueueElement( order_by: Option, ) -> Element { let watchlist_url = if patterns.is_empty() { - format_sstr!("/trakt/watchlist") + format_sstr!("/trakt/watchlist?offset=0&limit=10") } else { let patterns = patterns.join("_"); format_sstr!("/trakt/watched/list/{patterns}") @@ -336,7 +336,7 @@ fn MovieQueueElement( } }); let order_by = order_by.unwrap_or(OrderBy::Desc); - let limit = limit.unwrap_or(20); + let limit = limit.unwrap_or(10); let previous_button = if let Some(offset) = offset { if *offset < limit { None @@ -400,7 +400,7 @@ fn MovieQueueElement( cx.render(rsx! { br { a { - href: "javascript:updateMainArticle('/list/tvshows')", + href: "javascript:updateMainArticle('/list/tvshows?offset=0&limit=10')", "Go Back", } }, @@ -665,7 +665,7 @@ fn FindNewEpisodesElement( cx.render(rsx! { br { a { - href: "javascript:updateMainArticle('/list/tvshows')", + href: "javascript:updateMainArticle('/list/tvshows?offset=0&limit=10')", "Go Back", }, }, @@ -708,7 +708,14 @@ fn FindNewEpisodesElement( }) } -pub fn tvshows_body(show_map: TvShowsMap, tvshows: Vec) -> String { +pub fn tvshows_body( + show_map: TvShowsMap, + tvshows: Vec, + query: Option<&str>, + source: Option, + offset: Option, + limit: Option, +) -> String { let tvshows: HashSet<_> = tvshows .into_iter() .map(|s| { @@ -730,8 +737,19 @@ pub fn tvshows_body(show_map: TvShowsMap, tvshows: Vec) -> String }) .collect(); - let mut app = - VirtualDom::new_with_props(TvShowsElement, TvShowsElementProps { tvshows, watchlist }); + let query: Option = query.map(Into::into); + + let mut app = VirtualDom::new_with_props( + TvShowsElement, + TvShowsElementProps { + tvshows, + watchlist, + query, + source, + offset, + limit, + }, + ); drop(app.rebuild()); dioxus_ssr::render(&app) } @@ -741,102 +759,214 @@ fn TvShowsElement( cx: Scope, tvshows: HashSet, watchlist: HashSet, + query: Option, + source: Option, + offset: Option, + limit: Option, ) -> Element { let watchlist_shows = watchlist .iter() .filter(|item| tvshows.get(item.link.as_str()).is_none()); + let search_str = query + .as_ref() + .map_or_else(StackString::new, |s| format_sstr!("&query={s}")); + let source_str = source.map_or_else(StackString::new, |s| format_sstr!("&source={s}")); + + let offset = offset.unwrap_or(0) as usize; + let limit = limit.unwrap_or(10) as usize; + let mut shows: Vec<_> = tvshows.iter().chain(watchlist_shows).collect(); shows.sort_by(|x, y| x.show.cmp(&y.show)); - let entries = shows.into_iter().enumerate().map(|(idx, item)| { - let link = item.link.as_str(); - let title = &item.title; - let show = &item.show; - let has_watchlist = watchlist.contains(link); - let watchlist = if has_watchlist { - Some(rsx! { - a { - href: "javascript:updateMainArticle('/trakt/watched/list/{link}')", - "watchlist", + let entries = shows + .into_iter() + .skip(offset) + .take(limit) + .enumerate() + .map(|(idx, item)| { + let link = item.link.as_str(); + let title = &item.title; + let show = &item.show; + let has_watchlist = watchlist.contains(link); + let watchlist = if has_watchlist { + Some(rsx! { + a { + href: "javascript:updateMainArticle('/trakt/watched/list/{link}')", + "watchlist", + } + }) + } else { + None + }; + let title_element = if tvshows.contains(link) { + rsx! { + a { + href: "javascript:updateMainArticle('/list/queue/{show}')", + "{title}", + } } - }) - } else { - None - }; - let title_element = if tvshows.contains(link) { - rsx! { - a { - href: "javascript:updateMainArticle('/list/queue/{show}')", - "{title}", + } else { + rsx! { + a { + href: "javascript:updateMainArticle('/trakt/watched/list/{link}')", + "{title}", + } } - } - } else { - rsx! { - a { - href: "javascript:updateMainArticle('/trakt/watched/list/{link}')", - "{title}", + }; + let src = match item.source { + Some(TvShowSource::Netflix) => { + Some(rsx! {a {href: "https://netflix.com", target: "_blank", "netflix"}}) } - } - }; - let src = match item.source { - Some(TvShowSource::Netflix) => { - Some(rsx! {a {href: "https://netflix.com", target: "_blank", "netflix"}}) - } - Some(TvShowSource::Hulu) => { - Some(rsx! {a {href: "https://hulu.com", target: "_blank", "hulu"}}) - } - Some(TvShowSource::Amazon) => { - Some(rsx! {a {href: "https://amazon.com", target: "_blank", "amazon"}}) - } - _ => None, - }; - let sh = if has_watchlist { - rsx! { - td { - button { - "type": "submit", - id: "remove-link", - "onclick": "watchlist_rm('{link}');", - "remove to watchlist" + Some(TvShowSource::Hulu) => { + Some(rsx! {a {href: "https://hulu.com", target: "_blank", "hulu"}}) + } + Some(TvShowSource::Amazon) => { + Some(rsx! {a {href: "https://amazon.com", target: "_blank", "amazon"}}) + } + _ => None, + }; + let sh = if has_watchlist { + rsx! { + td { + button { + "type": "submit", + id: "remove-link", + "onclick": "watchlist_rm('{show}');", + "remove to watchlist" + } + } + } + } else { + rsx! { + td { + button { + "type": "submit", + id: "add-link", + "onclick": "watchlist_add('{show}');", + "add to watchlist" + } } } + }; + + rsx! { + tr { + key: "show-key-{idx}", + td {title_element}, + td { + a { + href: "https://www.imdb.com/title/{link}", + target: "_blank", + "imdb", + } + }, + td {src}, + td {watchlist}, + td {sh}, + } } + }); + + let options = [ + (TvShowSource::All, "all", ""), + (TvShowSource::Amazon, "amazon", "Amazon"), + (TvShowSource::Hulu, "hulu", "Hulu"), + (TvShowSource::Netflix, "netflix", "Netflix"), + ]; + + let previous_button = { + let search_str = search_str.clone(); + let source_str = source_str.clone(); + if offset < limit { + None } else { - rsx! { - td { - button { - "type": "submit", - id: "add-link", - "onclick": "watchlist_add('{link}');", - "add to watchlist" - } + let new_offset = offset - limit; + let search_str = search_str.clone(); + Some(rsx! { + button { + "type": "submit", + name: "previous", + value: "Previous", + "onclick": "updateMainArticle('/list/tvshows?limit={limit}&offset={new_offset}{search_str}{source_str}')", + "Previous", } + }) + } + }; + + let new_offset = offset + limit; + let next_button = { + let search_str = search_str.clone(); + let source_str = source_str.clone(); + rsx! { + button { + "type": "submit", + name: "next", + value: "Next", + "onclick": "updateMainArticle('/list/tvshows?limit={limit}&offset={new_offset}{search_str}{source_str}')", + "Next", } - }; + } + }; + let search = { + let source_str = source_str.clone(); rsx! { - tr { - key: "show-key-{idx}", - td {title_element}, - td { - a { - href: "https://www.imdb.com/title/{link}", - target: "_blank", - "imdb", - } + form { + action: "javascript:searchTvShows('/list/tvshows?offset=0&limit=10{source_str}')", + input { + "type": "text", + name: "search", + id: "tv_shows_search", }, - td {src}, - td {watchlist}, - td {sh}, + input { + "type": "button", + name: "submitSearch", + value: "Search", + "onclick": "searchTvShows('/list/tvshows?offset=0&limit=10{source_str}')", + } } } - }); + }; + + let source_button = { + let search_str = search_str.clone(); + rsx! { + form { + action: "javascript:sourceTvShows('/list/tvshows?offset=0&limit=10{search_str}')", + select { + id: "tv_shows_source_id", + "onchange": "sourceTvShows('/list/tvshows?offset=0&limit=10{search_str}');", + options.iter().enumerate().map(|(i, (s, v, l))| { + if (source.is_none() && *s == TvShowSource::All) || *source == Some(*s) { + rsx! { + option { + key: "source-option-{i}", + value: "{v}", + selected: true, + "{l}", + } + } + } else { + rsx! { + option { + key: "source-option-{i}", + value: "{v}", + "{l}", + } + } + } + }), + } + } + } + }; cx.render(rsx! { br { a { - href: "javascript:updateMainArticle('/list/watchlist')", + href: "javascript:updateMainArticle('/trakt/watchlist')", "Go Back", }, } @@ -844,6 +974,12 @@ fn TvShowsElement( href: "javascript:updateMainArticle('/trakt/watchlist')", "Watch List", }, + br { + source_button, + previous_button, + next_button, + } + search, br { button { name: "remcomout", @@ -859,7 +995,13 @@ fn TvShowsElement( }) } -pub fn watchlist_body(shows: WatchListMap) -> String { +pub fn watchlist_body( + shows: WatchListMap, + query: Option<&str>, + offset: Option, + limit: Option, + source: Option, +) -> String { let mut shows: Vec<_> = shows .into_iter() .map(|(_, (_, s, source))| WatchListEntry { @@ -869,7 +1011,17 @@ pub fn watchlist_body(shows: WatchListMap) -> String { }) .collect(); shows.sort(); - let mut app = VirtualDom::new_with_props(WatchlistElement, WatchlistElementProps { shows }); + let query = query.map(Into::into); + let mut app = VirtualDom::new_with_props( + WatchlistElement, + WatchlistElementProps { + shows, + query, + offset, + limit, + source, + }, + ); drop(app.rebuild()); dioxus_ssr::render(&app) } @@ -882,7 +1034,14 @@ struct WatchListEntry { } #[component] -fn WatchlistElement(cx: Scope, shows: Vec) -> Element { +fn WatchlistElement( + cx: Scope, + shows: Vec, + query: Option, + offset: Option, + limit: Option, + source: Option, +) -> Element { let shows = shows.iter().enumerate().map(|(idx, entry)| { let title = &entry.title; let link = &entry.link; @@ -942,13 +1101,123 @@ fn WatchlistElement(cx: Scope, shows: Vec) -> Element { } } }); + + let search_str = query + .as_ref() + .map_or_else(StackString::new, |s| format_sstr!("&query={s}")); + let source_str = source.map_or_else(StackString::new, |s| format_sstr!("&source={s}")); + + let offset = offset.unwrap_or(0) as usize; + let limit = limit.unwrap_or(10) as usize; + + let search = { + let source_str = source_str.clone(); + rsx! { + form { + action: "javascript:searchWatchlist('/trakt/watchlist?offset=0&limit=10{source_str}')", + input { + "type": "text", + name: "search", + id: "watchlist_search", + }, + input { + "type": "button", + name: "submitSearch", + value: "Search", + "onclick": "searchWatchlist('/trakt/watchlist?offset=0&limit=10{source_str}')", + } + } + } + }; + + let options = [ + (TvShowSource::All, "all", ""), + (TvShowSource::Amazon, "amazon", "Amazon"), + (TvShowSource::Hulu, "hulu", "Hulu"), + (TvShowSource::Netflix, "netflix", "Netflix"), + ]; + + let source_button = { + let search_str = search_str.clone(); + rsx! { + form { + action: "javascript:sourceWatchlist('/trakt/watchlist?offset=0&limit=10{search_str}')", + select { + id: "watchlist_source_id", + "onchange": "sourceWatchlist('/trakt/watchlist?offset=0&limit=10{search_str}');", + options.iter().enumerate().map(|(i, (s, v, l))| { + if (source.is_none() && *s == TvShowSource::All) || *source == Some(*s) { + rsx! { + option { + key: "source-option-{i}", + value: "{v}", + selected: true, + "{l}", + } + } + } else { + rsx! { + option { + key: "source-option-{i}", + value: "{v}", + "{l}", + } + } + } + }), + } + } + } + }; + + let previous_button = { + let search_str = search_str.clone(); + let source_str = source_str.clone(); + if offset < limit { + None + } else { + let new_offset = offset - limit; + let search_str = search_str.clone(); + Some(rsx! { + button { + "type": "submit", + name: "previous", + value: "Previous", + "onclick": "updateMainArticle('/trakt/watchlist?limit={limit}&offset={new_offset}{search_str}{source_str}')", + "Previous", + } + }) + } + }; + + let new_offset = offset + limit; + let next_button = { + let search_str = search_str.clone(); + let source_str = source_str.clone(); + rsx! { + button { + "type": "submit", + name: "next", + value: "Next", + "onclick": "updateMainArticle('/trakt/watchlist?limit={limit}&offset={new_offset}{search_str}{source_str}')", + "Next", + } + } + }; + cx.render(rsx! { br { a { - href: "javascript:updateMainArticle('/list/tvshows')", + href: "javascript:updateMainArticle('/trakt/watchlist?offset=0&limit=10')", "Go Back", }, } + search, + br { + source_button, + previous_button, + next_button, + } table { "border": "0", "align": "center", @@ -1140,7 +1409,7 @@ fn TraktCalHttpElement(cx: Scope, entries: Vec) -> Element { cx.render(rsx! { br { a { - href: "javascript:updateMainArticle('/list/tvshows')", + href: "javascript:updateMainArticle('/list/tvshows?offset=0&limit=10')", "Go Back", }, }, @@ -1344,10 +1613,12 @@ pub async fn parse_imdb_http_body( watchlist: WatchListMap, ) -> Result { let imdb_urls = imdb.parse_imdb_worker(opts).await?; + let show = opts.show.clone(); let mut app = VirtualDom::new_with_props( ParseImdbHttpElement, ParseImdbHttpElementProps { + show, imdb_urls, watchlist, }, @@ -1359,6 +1630,7 @@ pub async fn parse_imdb_http_body( #[component] fn ParseImdbHttpElement( cx: Scope, + show: StackString, imdb_urls: Vec>, watchlist: WatchListMap, ) -> Element { @@ -1388,7 +1660,7 @@ fn ParseImdbHttpElement( button { "type": "submit", id: "watchlist-rm-{idx}", - "onclick": "watchlist_rm('{imdb_url}');", + "onclick": "watchlist_rm('{show}');", "remove from watchlist", } } @@ -1399,7 +1671,7 @@ fn ParseImdbHttpElement( button { "type": "submit", id: "watchlist-add-{idx}", - "onclick": "watchlist_add('{imdb_url}');", + "onclick": "watchlist_add('{show}');", "add from watchlist", } } @@ -1497,7 +1769,7 @@ fn PlexElement( } } }); - let limit = limit.unwrap_or(20); + let limit = limit.unwrap_or(10); let section_str = section.map_or_else(|| StackString::from("null"), |s| format_sstr!("'{s}'")); let previous_button = if let Some(offset) = offset { if *offset < limit { @@ -1646,7 +1918,7 @@ fn PlexDetailElement( .map_or("", StackString::as_str); let filename = event.filename.as_ref().map_or("", StackString::as_str); - let limit = limit.unwrap_or(20); + let limit = limit.unwrap_or(10); let offset = offset.unwrap_or(0); cx.render(rsx! { diff --git a/movie_collection_http/src/movie_queue_requests.rs b/movie_collection_http/src/movie_queue_requests.rs index cac31b8..7190941 100644 --- a/movie_collection_http/src/movie_queue_requests.rs +++ b/movie_collection_http/src/movie_queue_requests.rs @@ -15,11 +15,7 @@ use movie_collection_lib::{ movie_queue::{MovieQueueDB, MovieQueueResult, MovieQueueRow, OrderBy}, parse_imdb::{ParseImdb, ParseImdbOptions}, pgpool::PgPool, - trakt_connection::TraktConnection, - trakt_utils::{ - get_watched_shows_db, get_watchlist_shows_db_map, TraktActions, WatchListMap, - WatchListShow, WatchedEpisode, - }, + trakt_utils::{get_watched_shows_db, get_watchlist_shows_db_map, WatchedEpisode}, tv_show_source::TvShowSource, }; @@ -28,16 +24,6 @@ use crate::{ ImdbRatingsWrapper, MovieCollectionRowWrapper, MovieQueueRowWrapper, TvShowSourceWrapper, }; -pub struct WatchlistShowsRequest {} - -impl WatchlistShowsRequest { - /// # Errors - /// Returns error if db query fails - pub async fn handle(&self, pool: &PgPool) -> Result { - get_watchlist_shows_db_map(pool).await.map_err(Into::into) - } -} - #[derive(Debug, Default)] pub struct MovieQueueRequest { pub patterns: Vec, @@ -102,37 +88,6 @@ impl ImdbSeasonsRequest { } } -pub struct WatchlistActionRequest { - pub action: TraktActions, - pub imdb_url: StackString, -} - -impl WatchlistActionRequest { - /// # Errors - /// Return error if api calls fail - pub async fn process( - self, - pool: &PgPool, - trakt: &TraktConnection, - ) -> Result { - match self.action { - TraktActions::Add => { - trakt.init().await; - if let Some(show) = trakt.get_watchlist_shows().await?.get(&self.imdb_url) { - show.insert_show(pool).await?; - } - } - TraktActions::Remove => { - if let Some(show) = WatchListShow::get_show_by_link(&self.imdb_url, pool).await? { - show.delete_show(pool).await?; - } - } - _ => {} - } - Ok(self.imdb_url) - } -} - pub struct WatchedShowsRequest { pub show: StackString, pub season: i32, @@ -220,7 +175,8 @@ impl ImdbShowRequest { let mock_stdout = MockStdout::new(); let stdout = StdoutChannel::with_mock_stdout(mock_stdout.clone(), mock_stdout); - let watchlist = get_watchlist_shows_db_map(pool).await?; + let watchlist = + get_watchlist_shows_db_map(pool, Some(&self.show), None, None, None).await?; let pi = ParseImdb::new(config, pool, &stdout); let body = parse_imdb_http_body(&pi, &self.into(), watchlist) .await? diff --git a/movie_collection_http/src/movie_queue_routes.rs b/movie_collection_http/src/movie_queue_routes.rs index 2c30eab..a22d937 100644 --- a/movie_collection_http/src/movie_queue_routes.rs +++ b/movie_collection_http/src/movie_queue_routes.rs @@ -36,7 +36,8 @@ use movie_collection_lib::{ plex_events::{PlexEvent, PlexFilename, PlexMetadata}, trakt_connection::TraktConnection, trakt_utils::{ - get_watchlist_shows_db_map, TraktActions, WatchListShow, WatchedEpisode, WatchedMovie, + get_watchlist_shows_db_map, watchlist_add, watchlist_rm, TraktActions, WatchListShow, + WatchedEpisode, WatchedMovie, }, transcode_service::{transcode_status, TranscodeService, TranscodeServiceRequest}, tv_show_source::TvShowSource, @@ -57,12 +58,12 @@ use crate::{ ImdbEpisodesUpdateRequest, ImdbRatingsSetSourceRequest, ImdbRatingsUpdateRequest, ImdbSeasonsRequest, ImdbShowRequest, MovieCollectionSyncRequest, MovieCollectionUpdateRequest, MovieQueueRequest, MovieQueueSyncRequest, - MovieQueueUpdateRequest, ParseImdbRequest, WatchlistActionRequest, + MovieQueueUpdateRequest, ParseImdbRequest, }, ImdbEpisodesWrapper, ImdbRatingsWrapper, LastModifiedResponseWrapper, MovieCollectionRowWrapper, MovieQueueRowWrapper, MusicCollectionWrapper, OrderByWrapper, PlexEventRequest, PlexEventWrapper, PlexFilenameRequest, PlexFilenameWrapper, - PlexMetadataWrapper, TraktActionsWrapper, TvShowSourceWrapper, + PlexMetadataWrapper, TraktActionsWrapper, TraktWatchlistRequest, TvShowSourceWrapper, }; pub type WarpResult = Result; @@ -761,7 +762,9 @@ struct ListTvShowsResponse(HtmlBase); pub async fn tvshows( #[filter = "LoggedUser::filter"] user: LoggedUser, #[data] state: AppState, + query: Query, ) -> WarpResult { + let query = query.into_inner(); let task = user .store_url_task(state.trakt.get_client(), &state.config, "/list/tvshows") .await; @@ -770,16 +773,35 @@ pub async fn tvshows( let mc = MovieCollection::new(&state.config, &state.db, &stdout); let shows: Vec<_> = mc - .print_tv_shows() + .print_tv_shows( + query.query.as_ref().map(StackString::as_str), + query.source.map(Into::into), + None, + None, + ) .await .map_err(Into::::into)? .try_collect() .await .map_err(Into::::into)?; - let show_map = get_watchlist_shows_db_map(&state.db) - .await - .map_err(Into::::into)?; - let body = tvshows_body(show_map, shows).into(); + let show_map = get_watchlist_shows_db_map( + &state.db, + query.query.as_ref().map(StackString::as_str), + query.source.map(Into::into), + None, + None, + ) + .await + .map_err(Into::::into)?; + let body = tvshows_body( + show_map, + shows, + query.query.as_ref().map(StackString::as_str), + query.source.map(Into::into), + query.offset, + query.limit, + ) + .into(); task.await.ok(); Ok(HtmlBase::new(body).into()) } @@ -1057,28 +1079,52 @@ struct TraktWatchlistResponse(HtmlBase); pub async fn trakt_watchlist( #[filter = "LoggedUser::filter"] user: LoggedUser, #[data] state: AppState, + query: Query, ) -> WarpResult { + let query = query.into_inner(); let task = user .store_url_task(state.trakt.get_client(), &state.config, "/trakt/watchlist") .await; - let shows = get_watchlist_shows_db_map(&state.db) - .await - .map_err(Into::::into)?; - let body = watchlist_body(shows).into(); + let shows = get_watchlist_shows_db_map( + &state.db, + query.query.as_ref().map(StackString::as_str), + query.source.map(Into::into), + query.offset, + query.limit, + ) + .await + .map_err(Into::::into)?; + let body = watchlist_body( + shows, + query.query.as_ref().map(StackString::as_str), + query.offset, + query.limit, + query.source.map(Into::into), + ) + .into(); task.await.ok(); Ok(HtmlBase::new(body).into()) } async fn watchlist_action_worker( trakt: &TraktConnection, + mc: &MovieCollection, action: TraktActions, - imdb_url: &str, + show: &str, ) -> HttpResult { trakt.init().await; let mut body = StackString::new(); match action { - TraktActions::Add => write!(body, "{}", trakt.add_watchlist_show(imdb_url).await?)?, - TraktActions::Remove => write!(body, "{}", trakt.remove_watchlist_show(imdb_url).await?)?, + TraktActions::Add => { + if let Some(result) = watchlist_add(trakt, mc, show, None).await? { + write!(body, "{result}")?; + } + } + TraktActions::Remove => { + if let Some(result) = watchlist_rm(trakt, mc, show).await? { + write!(body, "{result}")?; + } + } _ => (), } Ok(body) @@ -1092,10 +1138,10 @@ async fn watchlist_action_worker( )] struct TraktWatchlistActionResponse(HtmlBase); -#[post("/trakt/watchlist/{action}/{imdb_url}")] +#[post("/trakt/watchlist/{action}/{show}")] pub async fn trakt_watchlist_action( action: TraktActionsWrapper, - imdb_url: StackString, + show: StackString, #[filter = "LoggedUser::filter"] user: LoggedUser, #[data] state: AppState, ) -> WarpResult { @@ -1103,15 +1149,13 @@ pub async fn trakt_watchlist_action( .store_url_task( state.trakt.get_client(), &state.config, - &format_sstr!("/trakt/watchlist/{action}/{imdb_url}"), + &format_sstr!("/trakt/watchlist/{action}/{show}"), ) .await; - let req = WatchlistActionRequest { - action: action.into(), - imdb_url, - }; - let imdb_url = req.process(&state.db, &state.trakt).await?; - let body = watchlist_action_worker(&state.trakt, action.into(), &imdb_url).await?; + let mock_stdout = MockStdout::new(); + let stdout = StdoutChannel::with_mock_stdout(mock_stdout.clone(), mock_stdout.clone()); + let mc = MovieCollection::new(&state.config, &state.db, &stdout); + let body = watchlist_action_worker(&state.trakt, &mc, action.into(), &show).await?; task.await.ok(); Ok(HtmlBase::new(body).into()) } diff --git a/movie_collection_lib/Cargo.toml b/movie_collection_lib/Cargo.toml index 96413f8..e6e915f 100644 --- a/movie_collection_lib/Cargo.toml +++ b/movie_collection_lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "movie_collection_lib" -version = "0.10.29" +version = "0.10.30" authors = ["Daniel Boline "] edition = "2018" diff --git a/movie_collection_lib/src/make_queue.rs b/movie_collection_lib/src/make_queue.rs index 9a97801..6bbb5c8 100644 --- a/movie_collection_lib/src/make_queue.rs +++ b/movie_collection_lib/src/make_queue.rs @@ -61,7 +61,7 @@ pub async fn make_queue_worker( if do_shows { let shows: Vec<_> = mc - .print_tv_shows() + .print_tv_shows(None, None, None, None) .await? .map_ok(StackString::from_display) .try_collect() diff --git a/movie_collection_lib/src/movie_collection.rs b/movie_collection_lib/src/movie_collection.rs index 18cb309..2c311d4 100644 --- a/movie_collection_lib/src/movie_collection.rs +++ b/movie_collection_lib/src/movie_collection.rs @@ -715,18 +715,49 @@ impl MovieCollection { /// Returns error if db queries fail pub async fn print_tv_shows( &self, + search_query: Option<&str>, + source: Option, + offset: Option, + limit: Option, ) -> Result>, Error> { - let query = query!( + let mut constraints = vec![format_sstr!("c.istv")]; + if let Some(search_query) = search_query { + constraints.push(format_sstr!("b.show ilike '%{search_query}%'")); + } + if let Some(source) = source { + if source != TvShowSource::All { + constraints.push(format_sstr!("c.source = '{source}'")); + } + } + let query = format_sstr!( r#" SELECT b.show, c.link, c.title, c.source, count(*) as count FROM movie_queue a JOIN movie_collection b ON a.collection_idx=b.idx JOIN imdb_ratings c ON b.show_id=c.index - WHERE c.istv + {where_str} GROUP BY 1,2,3,4 ORDER BY 1,2,3,4 - "# + {offset} + {limit} + "#, + where_str = if constraints.is_empty() { + StackString::new() + } else { + format_sstr!("WHERE {}", constraints.join(" AND ")) + }, + offset = if let Some(offset) = offset { + format_sstr!("OFFSET {offset}") + } else { + StackString::new() + }, + limit = if let Some(limit) = limit { + format_sstr!("LIMIT {limit}") + } else { + StackString::new() + } ); + let query = query_dyn!(&query)?; let conn = self.pool.get().await?; query.fetch_streaming(&conn).await.map_err(Into::into) } diff --git a/movie_collection_lib/src/movie_queue.rs b/movie_collection_lib/src/movie_queue.rs index 174b031..e73c6a6 100644 --- a/movie_collection_lib/src/movie_queue.rs +++ b/movie_collection_lib/src/movie_queue.rs @@ -101,7 +101,7 @@ impl MovieQueueDB { let max_idx: i32 = tran .query(query, &[]) .await? - .get(0) + .first() .map_or(-1, |r| r.get(0)); if idx > max_idx || idx < 0 { return Ok(()); @@ -224,7 +224,7 @@ impl MovieQueueDB { let max_idx: i32 = tran .query(query, &[]) .await? - .get(0) + .first() .map_or(-1, |r| r.get(0)); let diff = max_idx - idx + 2; debug!("{} {} {}", max_idx, idx, diff); @@ -272,7 +272,7 @@ impl MovieQueueDB { /// Return error if db queries fail pub async fn get_max_queue_index(&self) -> Result { let query = r"SELECT max(idx) FROM movie_queue"; - if let Some(row) = self.pool.get().await?.query(query, &[]).await?.get(0) { + if let Some(row) = self.pool.get().await?.query(query, &[]).await?.first() { let max_idx: i32 = row.try_get(0)?; Ok(max_idx) } else { diff --git a/movie_collection_lib/src/parse_imdb.rs b/movie_collection_lib/src/parse_imdb.rs index 4d35d03..418af50 100644 --- a/movie_collection_lib/src/parse_imdb.rs +++ b/movie_collection_lib/src/parse_imdb.rs @@ -158,12 +158,12 @@ impl ParseImdb { let link = if let Some(link) = &opts.imdb_link { Some(link.clone()) } else { - results.get(0).map(|result| result.link.clone()) + results.first().map(|result| result.link.clone()) }; if !opts.tv { if opts.update_database { - if let Some(result) = results.get(0) { + if let Some(result) = results.first() { let show = &opts.show; let rating = result.rating; if let Some(s) = shows.get(&result.link) { diff --git a/movie_collection_lib/src/trakt_utils.rs b/movie_collection_lib/src/trakt_utils.rs index 16bbd5e..1d42e56 100644 --- a/movie_collection_lib/src/trakt_utils.rs +++ b/movie_collection_lib/src/trakt_utils.rs @@ -2,7 +2,7 @@ use anyhow::Error; use futures::{stream::FuturesUnordered, Stream, TryStreamExt}; use itertools::Itertools; use log::debug; -use postgres_query::{query, query_dyn, Error as PqError, FromSqlRow}; +use postgres_query::{query, query_dyn, Error as PqError, FromSqlRow, Query}; use serde::{Deserialize, Serialize}; use stack_string::{format_sstr, StackString}; use std::{ @@ -270,7 +270,13 @@ pub type WatchListMap = HashMap Result { +pub async fn get_watchlist_shows_db_map( + pool: &PgPool, + search_query: Option<&str>, + source: Option, + offset: Option, + limit: Option, +) -> Result { #[derive(FromSqlRow)] struct WatchlistShowDbMap { show: StackString, @@ -281,18 +287,52 @@ pub async fn get_watchlist_shows_db_map(pool: &PgPool) -> Result, + source: Option<&str>, + offset: Option, + limit: Option, ) -> Result>, Error> { - let query = query!( - r#" - SELECT b.show, b.link, a.title, a.year, b.source - FROM trakt_watchlist a - JOIN imdb_ratings b ON a.show=b.show - "# + let mut constraints = Vec::new(); + if let Some(search_query) = search_query { + constraints.push(format_sstr!("b.show ilike '%{search_query}%'")); + } + if let Some(source) = source { + if source != "all" { + constraints.push(format_sstr!("b.source = '{source}'")); + } + } + let query = format_sstr!( + " + SELECT b.show, b.link, a.title, a.year, b.source + FROM trakt_watchlist a + JOIN imdb_ratings b ON a.show=b.show + {where_str} + ORDER BY 1 + {offset} + {limit} + ", + where_str = if constraints.is_empty() { + StackString::new() + } else { + format_sstr!("WHERE {}", constraints.join(" AND ")) + }, + offset = if let Some(offset) = offset { + format_sstr!("OFFSET {offset}") + } else { + StackString::new() + }, + limit = if let Some(limit) = limit { + format_sstr!("LIMIT {limit}") + } else { + StackString::new() + } ); + let query: Query = query_dyn!(&query)?; let conn = pool.get().await?; query.fetch_streaming(&conn).await.map_err(Into::into) } - _get_watchlist_shows_db_map(pool) + let source = source.map(TvShowSource::to_str); + _get_watchlist_shows_db_map(pool, search_query, source, offset, limit) .await? .map_ok(|row| { let source: Option = match row.source { @@ -713,19 +753,21 @@ async fn trakt_cal_list(trakt: &TraktConnection, mc: &MovieCollection) -> Result Ok(()) } -async fn watchlist_add( +/// # Errors +/// Return error if db query fails +pub async fn watchlist_add( trakt: &TraktConnection, mc: &MovieCollection, show: &str, imdb_link: Option<&str>, -) -> Result<(), Error> { +) -> Result, Error> { trakt.init().await; let imdb_url = if let Some(link) = imdb_link { link.into() } else if let Some(link) = get_imdb_url_from_show(mc, show).await? { link } else { - return Ok(()); + return Ok(None); }; let result = trakt.add_watchlist_show(&imdb_url).await?; mc.stdout.send(format_sstr!("result: {result}")); @@ -739,14 +781,16 @@ async fn watchlist_add( debug!("INSERT SHOW {}", show_obj); show_obj.insert_show(&mc.pool).await?; } - Ok(()) + Ok(Some(result)) } -async fn watchlist_rm( +/// # Errors +/// Return error if db query fails +pub async fn watchlist_rm( trakt: &TraktConnection, mc: &MovieCollection, show: &str, -) -> Result<(), Error> { +) -> Result, Error> { if let Some(imdb_url) = get_imdb_url_from_show(mc, show).await? { let imdb_url_ = imdb_url.clone(); trakt.init().await; @@ -755,8 +799,10 @@ async fn watchlist_rm( if let Some(show) = WatchListShow::get_show_by_link(&imdb_url, &mc.pool).await? { show.delete_show(&mc.pool).await?; } + Ok(Some(result)) + } else { + Ok(None) } - Ok(()) } async fn watchlist_list(mc: &MovieCollection) -> Result<(), Error> { diff --git a/templates/scripts.js b/templates/scripts.js index c84babf..67ef4c0 100644 --- a/templates/scripts.js +++ b/templates/scripts.js @@ -58,8 +58,8 @@ function delete_show(index, offset, order_by) { } xmlhttp.send(null); } -function watchlist_add(link) { - let url = "/trakt/watchlist/add/" + link +function watchlist_add(show) { + let url = "/trakt/watchlist/add/" + show let xmlhttp = new XMLHttpRequest(); xmlhttp.open("POST", url, true); xmlhttp.onload = function nothing() { @@ -70,8 +70,8 @@ function watchlist_add(link) { let out = "requested " + link document.getElementById("remcomoutput").innerHTML = out; } -function watchlist_rm(link) { - let url = "/trakt/watchlist/rm/" + link +function watchlist_rm(show) { + let url = "/trakt/watchlist/rm/" + show let xmlhttp = new XMLHttpRequest(); xmlhttp.open("POST", url, true); xmlhttp.onload = function nothing() { @@ -220,4 +220,24 @@ function loadPlex(offset=0, limit=10, section=null) { url = url + '§ion_type=' + section; } updateMainArticle(url); -} \ No newline at end of file +} +function searchTvShows(link) { + let search = document.getElementById('tv_shows_search').value; + let url = `${link}&query=${search}`; + updateMainArticle(url); +} +function sourceTvShows(link) { + let source = document.getElementById('tv_shows_source_id').value; + let url = `${link}&source=${source}`; + updateMainArticle(url); +} +function searchWatchlist(link) { + let search = document.getElementById('watchlist_search').value; + let url = `${link}&query=${search}`; + updateMainArticle(url); +} +function sourceWatchlist(link) { + let source = document.getElementById('watchlist_source_id').value; + let url = `${link}&source=${source}`; + updateMainArticle(url); +} diff --git a/transcode_lib/Cargo.toml b/transcode_lib/Cargo.toml index 74338d2..21a1345 100644 --- a/transcode_lib/Cargo.toml +++ b/transcode_lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "transcode_lib" -version = "0.10.29" +version = "0.10.30" authors = ["Daniel Boline "] edition = "2018"