Full support for editing movies and managing tags

This commit is contained in:
2023-08-08 10:50:06 -06:00
parent c946d916ad
commit 7f3dd18478
16 changed files with 293 additions and 198 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "managarr"
version = "0.0.12"
version = "0.0.13"
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
description = "A TUI for managing *arr servers"
keywords = ["managarr", "tui-rs", "dashboard", "servarr"]
+2 -2
View File
@@ -84,10 +84,10 @@ tautulli:
- [x] Trigger automatic searches for movies
- [x] Trigger refresh and disk scan for movies, downloads, and collections
- [x] Manually search for movies
- [ ] Edit movies
- [x] Edit movies
- [ ] Manage your quality profiles
- [ ] Manage your quality definitions
- [ ] Manage your tags
- [x] Manage your tags
- [ ] Manage your indexers
### Sonarr
+43 -1
View File
@@ -25,6 +25,7 @@ pub struct App {
pub client: Client,
pub title: &'static str,
pub tick_until_poll: u64,
pub ticks_until_scroll: u64,
pub tick_count: u64,
pub last_tick: Instant,
pub network_tick_frequency: Duration,
@@ -48,6 +49,7 @@ impl App {
pub async fn dispatch_network_event(&mut self, action: NetworkEvent) {
debug!("Dispatching network event: {:?}", action);
self.is_loading = true;
if let Some(network_tx) = &self.network_tx {
if let Err(e) = network_tx.send(action).await {
self.is_loading = false;
@@ -136,6 +138,7 @@ impl Default for App {
client: Client::new(),
title: "Managarr",
tick_until_poll: 50,
ticks_until_scroll: 4,
tick_count: 0,
network_tick_frequency: Duration::from_secs(20),
last_tick: Instant::now(),
@@ -178,16 +181,55 @@ impl Default for RadarrConfig {
#[cfg(test)]
mod tests {
use std::time::Duration;
use anyhow::anyhow;
use pretty_assertions::{assert_eq, assert_str_eq};
use tokio::sync::mpsc;
use crate::app::radarr::{ActiveRadarrBlock, RadarrData};
use crate::app::{App, Data, RadarrConfig, DEFAULT_ROUTE};
use crate::models::HorizontallyScrollableText;
use crate::models::{HorizontallyScrollableText, Route, TabRoute};
use crate::network::radarr_network::RadarrEvent;
use crate::network::NetworkEvent;
#[test]
fn test_app_default() {
let app = App::default();
assert_eq!(app.navigation_stack, vec![DEFAULT_ROUTE]);
assert!(app.network_tx.is_none());
assert_eq!(app.error, HorizontallyScrollableText::default());
assert_eq!(app.response, String::default());
assert_eq!(app.server_tabs.index, 0);
assert_eq!(
app.server_tabs.tabs,
vec![
TabRoute {
title: "Radarr".to_owned(),
route: ActiveRadarrBlock::Movies.into(),
help: "<↑↓> scroll | ←→ change tab | <tab> change servarr | <q> quit ".to_owned(),
contextual_help: None,
},
TabRoute {
title: "Sonarr".to_owned(),
route: Route::Sonarr,
help: "<tab> change servarr | <q> quit ".to_owned(),
contextual_help: None,
}
]
);
assert_str_eq!(app.title, "Managarr");
assert_eq!(app.tick_until_poll, 50);
assert_eq!(app.ticks_until_scroll, 4);
assert_eq!(app.tick_count, 0);
assert_eq!(app.network_tick_frequency, Duration::from_secs(20));
assert!(!app.is_loading);
assert!(!app.is_routing);
assert!(!app.should_refresh);
assert!(!app.should_ignore_quit_key);
}
#[test]
fn test_navigation_stack_methods() {
let mut app = App::default();
+59 -36
View File
@@ -1,4 +1,3 @@
use std::collections::HashMap;
use std::time::Duration;
use bimap::BiMap;
@@ -28,7 +27,7 @@ pub struct RadarrData {
pub movie_quality_profile_list: StatefulList<String>,
pub selected_block: ActiveRadarrBlock,
pub downloads: StatefulTable<DownloadRecord>,
pub quality_profile_map: HashMap<u64, String>,
pub quality_profile_map: BiMap<u64, String>,
pub tags_map: BiMap<u64, String>,
pub movie_details: ScrollableText,
pub file_details: String,
@@ -112,7 +111,7 @@ impl RadarrData {
.movie_minimum_availability_list
.set_items(Vec::from_iter(MinimumAvailability::iter()));
let mut quality_profile_names: Vec<String> =
self.quality_profile_map.values().cloned().collect();
self.quality_profile_map.right_values().cloned().collect();
quality_profile_names.sort();
self
.movie_quality_profile_list
@@ -161,7 +160,7 @@ impl RadarrData {
let quality_profile_name = self
.quality_profile_map
.get(&quality_profile_id.as_u64().unwrap())
.get_by_left(&quality_profile_id.as_u64().unwrap())
.unwrap();
let quality_profile_index = self
.movie_quality_profile_list
@@ -190,7 +189,7 @@ impl Default for RadarrData {
selected_block: ActiveRadarrBlock::AddMovieSelectMonitor,
filtered_movies: StatefulTable::default(),
downloads: StatefulTable::default(),
quality_profile_map: HashMap::default(),
quality_profile_map: BiMap::default(),
tags_map: BiMap::default(),
file_details: String::default(),
audio_details: String::default(),
@@ -289,6 +288,7 @@ pub enum ActiveRadarrBlock {
AddMovieSelectMonitor,
AddMovieConfirmPrompt,
AddMovieTagsInput,
AddMovieEmptySearchResults,
AutomaticallySearchMoviePrompt,
Collections,
CollectionDetails,
@@ -322,9 +322,10 @@ pub enum ActiveRadarrBlock {
ViewMovieOverview,
}
pub const ADD_MOVIE_BLOCKS: [ActiveRadarrBlock; 8] = [
pub const ADD_MOVIE_BLOCKS: [ActiveRadarrBlock; 9] = [
ActiveRadarrBlock::AddMovieSearchInput,
ActiveRadarrBlock::AddMovieSearchResults,
ActiveRadarrBlock::AddMovieEmptySearchResults,
ActiveRadarrBlock::AddMoviePrompt,
ActiveRadarrBlock::AddMovieSelectMinimumAvailability,
ActiveRadarrBlock::AddMovieSelectMonitor,
@@ -444,24 +445,19 @@ impl App {
pub(super) async fn dispatch_by_radarr_block(&mut self, active_radarr_block: &ActiveRadarrBlock) {
match active_radarr_block {
ActiveRadarrBlock::Collections => {
self.is_loading = true;
self
.dispatch_network_event(RadarrEvent::GetCollections.into())
.await;
self.check_for_prompt_action().await;
}
ActiveRadarrBlock::CollectionDetails => {
self.is_loading = true;
self.populate_movie_collection_table().await;
self.is_loading = false;
self.check_for_prompt_action().await;
}
ActiveRadarrBlock::Downloads => {
self.is_loading = true;
self
.dispatch_network_event(RadarrEvent::GetDownloads.into())
.await;
self.check_for_prompt_action().await;
}
ActiveRadarrBlock::Movies => {
self
@@ -470,54 +466,42 @@ impl App {
self
.dispatch_network_event(RadarrEvent::GetDownloads.into())
.await;
self.check_for_prompt_action().await;
}
ActiveRadarrBlock::AddMovieSearchResults => {
self.is_loading = true;
self
.dispatch_network_event(RadarrEvent::SearchNewMovie.into())
.await;
self.check_for_prompt_action().await;
}
ActiveRadarrBlock::MovieDetails | ActiveRadarrBlock::FileInfo => {
self.is_loading = true;
self
.dispatch_network_event(RadarrEvent::GetMovieDetails.into())
.await;
self.check_for_prompt_action().await;
}
ActiveRadarrBlock::MovieHistory => {
self.is_loading = true;
self
.dispatch_network_event(RadarrEvent::GetMovieHistory.into())
.await;
self.check_for_prompt_action().await;
}
ActiveRadarrBlock::Cast | ActiveRadarrBlock::Crew => {
if self.data.radarr_data.movie_cast.items.is_empty()
|| self.data.radarr_data.movie_crew.items.is_empty()
{
self.is_loading = true;
self
.dispatch_network_event(RadarrEvent::GetMovieCredits.into())
.await;
}
self.check_for_prompt_action().await;
}
ActiveRadarrBlock::ManualSearch => {
if self.data.radarr_data.movie_releases.items.is_empty() && !self.is_loading {
self.is_loading = true;
self
.dispatch_network_event(RadarrEvent::GetReleases.into())
.await;
}
self.check_for_prompt_action().await;
}
_ => (),
}
self.check_for_prompt_action().await;
self.reset_tick_count();
}
@@ -563,16 +547,20 @@ impl App {
.unwrap_or_else(|| Duration::from_secs(0))
.is_zero()
{
self
.dispatch_network_event(RadarrEvent::GetQualityProfiles.into())
.await;
self
.dispatch_network_event(RadarrEvent::GetTags.into())
.await;
self.refresh_metadata().await;
self.dispatch_by_radarr_block(&active_radarr_block).await;
}
}
async fn refresh_metadata(&mut self) {
self
.dispatch_network_event(RadarrEvent::GetQualityProfiles.into())
.await;
self
.dispatch_network_event(RadarrEvent::GetTags.into())
.await;
}
async fn populate_movie_collection_table(&mut self) {
let collection_movies = if !self.data.radarr_data.filtered_collections.items.is_empty() {
self
@@ -735,8 +723,6 @@ pub mod radarr_test_utils {
#[cfg(test)]
mod tests {
mod radarr_data_tests {
use std::collections::HashMap;
use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
@@ -824,7 +810,7 @@ mod tests {
#[test]
fn test_populate_movie_preferences_lists() {
let mut radarr_data = RadarrData {
quality_profile_map: HashMap::from([
quality_profile_map: BiMap::from_iter([
(2222, "HD - 1080p".to_owned()),
(1111, "Any".to_owned()),
]),
@@ -853,7 +839,7 @@ mod tests {
edit_path: HorizontallyScrollableText::default(),
edit_tags: HorizontallyScrollableText::default(),
edit_monitored: None,
quality_profile_map: HashMap::from([
quality_profile_map: BiMap::from_iter([
(2222, "HD - 1080p".to_owned()),
(1111, "Any".to_owned()),
]),
@@ -1064,6 +1050,25 @@ mod tests {
#[tokio::test]
async fn test_dispatch_by_collection_details_block() {
let (mut app, _) = construct_app_unit();
app.data.radarr_data.collections.set_items(vec![Collection {
movies: Some(vec![CollectionMovie::default()]),
..Collection::default()
}]);
app
.dispatch_by_radarr_block(&ActiveRadarrBlock::CollectionDetails)
.await;
assert!(!app.is_loading);
assert!(!app.data.radarr_data.collection_movies.items.is_empty());
assert_eq!(app.tick_count, 0);
assert!(!app.data.radarr_data.prompt_confirm);
}
#[tokio::test]
async fn test_dispatch_by_collection_details_block_with_add_movie() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddMovie);
@@ -1076,7 +1081,7 @@ mod tests {
.dispatch_by_radarr_block(&ActiveRadarrBlock::CollectionDetails)
.await;
assert!(!app.is_loading);
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
RadarrEvent::AddMovie.into()
@@ -1111,7 +1116,7 @@ mod tests {
.dispatch_by_radarr_block(&ActiveRadarrBlock::Movies)
.await;
assert!(!app.is_loading);
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetMovies.into()
@@ -1360,6 +1365,24 @@ mod tests {
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
}
#[tokio::test]
async fn test_radarr_refresh_metadata() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app.is_routing = true;
app.refresh_metadata().await;
assert_eq!(
sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetQualityProfiles.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetTags.into()
);
assert!(app.is_loading);
}
#[tokio::test]
async fn test_radarr_on_tick_first_render() {
let (mut app, mut sync_network_rx) = construct_app_unit();
@@ -258,7 +258,7 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for AddMovieHandler<'a> {
self.app.data.radarr_data.reset_search();
self.app.should_ignore_quit_key = false;
}
ActiveRadarrBlock::AddMovieSearchResults => {
ActiveRadarrBlock::AddMovieSearchResults | ActiveRadarrBlock::AddMovieEmptySearchResults => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.add_searched_movies = StatefulTable::default();
self.app.should_ignore_quit_key = true;
@@ -295,7 +295,6 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for AddMovieHandler<'a> {
}
#[cfg(test)]
#[allow(unused_imports)]
mod tests {
use pretty_assertions::assert_str_eq;
@@ -376,7 +375,6 @@ mod tests {
}
mod test_handle_home_end {
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::{
@@ -482,8 +480,7 @@ mod tests {
}
mod test_handle_submit {
use std::collections::HashMap;
use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
@@ -524,7 +521,7 @@ mod tests {
.add_searched_movies
.set_items(vec![AddMovieSearchResult::default()]);
app.data.radarr_data.quality_profile_map =
HashMap::from([(1, "B - Test 2".to_owned()), (0, "A - Test 1".to_owned())]);
BiMap::from_iter([(1, "B - Test 2".to_owned()), (0, "A - Test 1".to_owned())]);
AddMovieHandler::with(
&SUBMIT_KEY,
@@ -782,11 +779,17 @@ mod tests {
);
}
#[test]
fn test_add_movie_search_results_esc() {
#[rstest]
fn test_add_movie_search_results_esc(
#[values(
ActiveRadarrBlock::AddMovieSearchResults,
ActiveRadarrBlock::AddMovieEmptySearchResults
)]
active_radarr_block: ActiveRadarrBlock,
) {
let mut app = App::default();
app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into());
app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchResults.into());
app.push_navigation_stack(active_radarr_block.into());
app
.data
.radarr_data
@@ -796,13 +799,7 @@ mod tests {
HorizontallyScrollableText
));
AddMovieHandler::with(
&ESC_KEY,
&mut app,
&ActiveRadarrBlock::AddMovieSearchResults,
&None,
)
.handle();
AddMovieHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle();
assert_eq!(
app.get_current_route(),
@@ -164,8 +164,7 @@ mod tests {
}
mod test_handle_submit {
use std::collections::HashMap;
use bimap::BiMap;
use pretty_assertions::assert_eq;
use crate::models::radarr_models::Movie;
@@ -183,7 +182,7 @@ mod tests {
.collection_movies
.set_items(vec![CollectionMovie::default()]);
app.data.radarr_data.quality_profile_map =
HashMap::from([(1, "B - Test 2".to_owned()), (0, "A - Test 1".to_owned())]);
BiMap::from_iter([(1, "B - Test 2".to_owned()), (0, "A - Test 1".to_owned())]);
app.data.radarr_data.selected_block = ActiveRadarrBlock::AddMovieConfirmPrompt;
CollectionDetailsHandler::with(
@@ -211,7 +211,6 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for EditMovieHandler<'a> {
}
#[cfg(test)]
#[allow(unused_imports)]
mod tests {
use pretty_assertions::assert_str_eq;
@@ -221,7 +220,7 @@ mod tests {
use crate::event::Key;
use crate::handlers::radarr_handlers::edit_movie_handler::EditMovieHandler;
use crate::handlers::KeyEventHandler;
use crate::models::radarr_models::{MinimumAvailability, Monitor};
use crate::models::radarr_models::MinimumAvailability;
mod test_handle_scroll_up_and_down {
use pretty_assertions::assert_eq;
@@ -316,7 +315,7 @@ mod tests {
mod test_handle_left_right_action {
use rstest::rstest;
use crate::{test_text_box_home_end_keys, test_text_box_left_right_keys};
use crate::test_text_box_left_right_keys;
use super::*;
@@ -353,9 +352,7 @@ mod tests {
}
mod test_handle_submit {
use std::collections::HashMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use pretty_assertions::assert_eq;
use rstest::rstest;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
+1 -3
View File
@@ -543,7 +543,7 @@ mod radarr_handler_test_utils {
edit_path: HorizontallyScrollableText::default(),
edit_tags: HorizontallyScrollableText::default(),
edit_monitored: None,
quality_profile_map: HashMap::from([
quality_profile_map: BiMap::from_iter([
(2222, "HD - 1080p".to_owned()),
(1111, "Any".to_owned()),
]),
@@ -1249,8 +1249,6 @@ mod tests {
}
mod test_handle_key_char {
use std::collections::HashMap;
use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
@@ -326,7 +326,6 @@ fn sort_releases_by_selected_field(
}
#[cfg(test)]
#[allow(unused_imports)]
mod tests {
use pretty_assertions::assert_str_eq;
use rstest::rstest;
@@ -712,13 +711,11 @@ mod tests {
}
mod test_handle_esc {
use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use pretty_assertions::assert_eq;
use rstest::rstest;
use crate::app::radarr::radarr_test_utils::create_test_radarr_data;
use crate::assert_movie_info_tabs_reset;
use crate::models::HorizontallyScrollableText;
use super::*;
@@ -770,8 +767,6 @@ mod tests {
}
mod test_handle_key_char {
use std::collections::HashMap;
use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
@@ -779,7 +774,6 @@ mod tests {
use crate::app::radarr::radarr_test_utils::create_test_radarr_data;
use crate::app::radarr::RadarrData;
use crate::handlers::radarr_handlers::RadarrHandler;
use crate::models::radarr_models::{MinimumAvailability, Movie};
use crate::models::HorizontallyScrollableText;
use crate::models::StatefulTable;
-4
View File
@@ -81,10 +81,6 @@ async fn start_ui(app: &Arc<Mutex<App>>) -> Result<()> {
loop {
let mut app = app.lock().await;
if is_first_render {
app.is_loading = true;
}
terminal.draw(|f| ui(f, &mut app))?;
match input_events.next()? {
+16 -9
View File
@@ -218,11 +218,14 @@ impl HorizontallyScrollableText {
*self.offset.borrow_mut() = 0;
}
pub fn scroll_left_or_reset(&self, width: usize, is_current_selection: bool) {
if is_current_selection && self.text.len() >= width && *self.offset.borrow() < self.text.len() {
self.scroll_left();
} else {
self.reset_offset();
pub fn scroll_left_or_reset(&self, width: usize, is_current_selection: bool, can_scroll: bool) {
if can_scroll {
if is_current_selection && self.text.len() >= width && *self.offset.borrow() < self.text.len()
{
self.scroll_left();
} else {
self.reset_offset();
}
}
}
@@ -554,19 +557,23 @@ mod tests {
let test_text = "Test string";
let horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned());
horizontally_scrollable_text.scroll_left_or_reset(width, true);
horizontally_scrollable_text.scroll_left_or_reset(width, true, true);
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 1);
horizontally_scrollable_text.scroll_left_or_reset(width, false);
horizontally_scrollable_text.scroll_left_or_reset(width, false, true);
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0);
horizontally_scrollable_text.scroll_left_or_reset(width, true);
horizontally_scrollable_text.scroll_left_or_reset(width, true, false);
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0);
horizontally_scrollable_text.scroll_left_or_reset(width, true, true);
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 1);
horizontally_scrollable_text.scroll_left_or_reset(test_text.len(), false);
horizontally_scrollable_text.scroll_left_or_reset(test_text.len(), false, true);
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0);
}
+52 -13
View File
@@ -224,11 +224,15 @@ impl<'a> Network<'a> {
self
.handle_request::<(), Vec<AddMovieSearchResult>>(request_props, |movie_vec, mut app| {
app
.data
.radarr_data
.add_searched_movies
.set_items(movie_vec)
if movie_vec.is_empty() {
app.pop_and_push_navigation_stack(ActiveRadarrBlock::AddMovieEmptySearchResults.into());
} else {
app
.data
.radarr_data
.add_searched_movies
.set_items(movie_vec);
}
})
.await;
}
@@ -371,7 +375,7 @@ impl<'a> Network<'a> {
.data
.radarr_data
.quality_profile_map
.get(&quality_profile_id.as_u64().unwrap())
.get_by_left(&quality_profile_id.as_u64().unwrap())
.unwrap()
.to_owned();
let imdb_rating = if let Some(rating) = ratings.imdb {
@@ -1040,7 +1044,6 @@ fn get_movie_status(has_file: bool, downloads_vec: &[DownloadRecord], movie_id:
#[cfg(test)]
mod test {
use std::collections::HashMap;
use std::sync::Arc;
use bimap::BiMap;
@@ -1417,6 +1420,41 @@ mod test {
);
}
#[tokio::test]
async fn test_handle_search_new_movie_event_no_results() {
let (async_server, app_arc, _server) = mock_radarr_api(
RequestMethod::Get,
None,
Some(json!([])),
format!(
"{}?term=test%20term",
RadarrEvent::SearchNewMovie.resource()
)
.as_str(),
)
.await;
app_arc.lock().await.data.radarr_data.search = "test term".to_owned().into();
let network = Network::new(reqwest::Client::new(), &app_arc);
network
.handle_radarr_event(RadarrEvent::SearchNewMovie)
.await;
async_server.assert_async().await;
assert!(app_arc
.lock()
.await
.data
.radarr_data
.add_searched_movies
.items
.is_empty());
assert_eq!(
app_arc.lock().await.get_current_route(),
&ActiveRadarrBlock::AddMovieEmptySearchResults.into()
);
}
#[tokio::test]
async fn test_handle_trigger_automatic_search_event() {
let (async_server, app_arc, _server) = mock_radarr_api(
@@ -1551,7 +1589,7 @@ mod test {
.movies
.set_items(vec![movie()]);
app_arc.lock().await.data.radarr_data.quality_profile_map =
HashMap::from([(2222, "HD - 1080p".to_owned())]);
BiMap::from_iter([(2222, "HD - 1080p".to_owned())]);
let network = Network::new(reqwest::Client::new(), &app_arc);
network
@@ -1657,7 +1695,7 @@ mod test {
.movies
.set_items(vec![movie()]);
app_arc.lock().await.data.radarr_data.quality_profile_map =
HashMap::from([(2222, "HD - 1080p".to_owned())]);
BiMap::from_iter([(2222, "HD - 1080p".to_owned())]);
let network = Network::new(reqwest::Client::new(), &app_arc);
network
@@ -1852,7 +1890,7 @@ mod test {
async_server.assert_async().await;
assert_eq!(
app_arc.lock().await.data.radarr_data.quality_profile_map,
HashMap::from([(2222u64, "HD - 1080p".to_owned())])
BiMap::from_iter([(2222u64, "HD - 1080p".to_owned())])
);
}
@@ -2064,7 +2102,8 @@ mod test {
free_space: Number::from(21990232555520u64),
},
];
app.data.radarr_data.quality_profile_map = HashMap::from([(2222, "HD - 1080p".to_owned())]);
app.data.radarr_data.quality_profile_map =
BiMap::from_iter([(2222, "HD - 1080p".to_owned())]);
app.data.radarr_data.tags_map =
BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]);
app.data.radarr_data.edit_tags = "usenet, testing".to_owned().into();
@@ -2153,7 +2192,7 @@ mod test {
..movie()
}]);
app.data.radarr_data.quality_profile_map =
HashMap::from([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]);
BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]);
}
let network = Network::new(reqwest::Client::new(), &app_arc);
@@ -2208,7 +2247,7 @@ mod test {
.movie_quality_profile_list
.set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]);
app.data.radarr_data.quality_profile_map =
HashMap::from_iter([(1, "Any".to_owned()), (2, "HD - 1080p".to_owned())]);
BiMap::from_iter([(1, "Any".to_owned()), (2, "HD - 1080p".to_owned())]);
}
let network = Network::new(reqwest::Client::new(), &app_arc);
+91 -92
View File
@@ -31,7 +31,9 @@ pub(super) fn draw_add_movie_search_popup<B: Backend>(
) {
if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() {
match active_radarr_block {
ActiveRadarrBlock::AddMovieSearchInput | ActiveRadarrBlock::AddMovieSearchResults => {
ActiveRadarrBlock::AddMovieSearchInput
| ActiveRadarrBlock::AddMovieSearchResults
| ActiveRadarrBlock::AddMovieEmptySearchResults => {
draw_add_movie_search(f, app, area);
}
ActiveRadarrBlock::AddMoviePrompt
@@ -108,6 +110,10 @@ fn draw_add_movie_search<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area:
.alignment(Alignment::Center);
f.render_widget(help_paragraph, chunks[2]);
}
ActiveRadarrBlock::AddMovieEmptySearchResults => {
f.render_widget(layout_block(), chunks[1]);
draw_error_popup(f, "No movies found matching your query!");
}
ActiveRadarrBlock::AddMovieSearchResults
| ActiveRadarrBlock::AddMoviePrompt
| ActiveRadarrBlock::AddMovieSelectMonitor
@@ -122,99 +128,92 @@ fn draw_add_movie_search<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area:
.alignment(Alignment::Center);
f.render_widget(help_paragraph, chunks[2]);
if app.data.radarr_data.add_searched_movies.items.is_empty()
&& !app.is_loading
&& !app.is_routing
{
f.render_widget(layout_block(), chunks[1]);
draw_error_popup(f, "No movies found matching your query!");
} else {
draw_table(
f,
chunks[1],
layout_block(),
TableProps {
content: &mut app.data.radarr_data.add_searched_movies,
table_headers: vec![
"",
"Title",
"Year",
"Runtime",
"IMDB",
"Rotten Tomatoes",
"Genres",
],
constraints: vec![
Constraint::Percentage(2),
Constraint::Percentage(27),
Constraint::Percentage(8),
Constraint::Percentage(10),
Constraint::Percentage(8),
Constraint::Percentage(14),
Constraint::Percentage(28),
],
help: None,
},
|movie| {
let (hours, minutes) = convert_runtime(movie.runtime.as_u64().unwrap());
let imdb_rating = movie
.ratings
.imdb
.clone()
.unwrap_or_default()
.value
.as_f64()
.unwrap();
let rotten_tomatoes_rating = movie
.ratings
.rotten_tomatoes
.clone()
.unwrap_or_default()
.value
.as_u64()
.unwrap();
let imdb_rating = if imdb_rating == 0.0 {
String::default()
} else {
format!("{:.1}", imdb_rating)
};
let rotten_tomatoes_rating = if rotten_tomatoes_rating == 0 {
String::default()
} else {
format!("{}%", rotten_tomatoes_rating)
};
let in_library = if app
.data
.radarr_data
.movies
.items
.iter()
.any(|mov| mov.tmdb_id == movie.tmdb_id)
{
""
} else {
""
};
draw_table(
f,
chunks[1],
layout_block(),
TableProps {
content: &mut app.data.radarr_data.add_searched_movies,
table_headers: vec![
"",
"Title",
"Year",
"Runtime",
"IMDB",
"Rotten Tomatoes",
"Genres",
],
constraints: vec![
Constraint::Percentage(2),
Constraint::Percentage(27),
Constraint::Percentage(8),
Constraint::Percentage(10),
Constraint::Percentage(8),
Constraint::Percentage(14),
Constraint::Percentage(28),
],
help: None,
},
|movie| {
let (hours, minutes) = convert_runtime(movie.runtime.as_u64().unwrap());
let imdb_rating = movie
.ratings
.imdb
.clone()
.unwrap_or_default()
.value
.as_f64()
.unwrap();
let rotten_tomatoes_rating = movie
.ratings
.rotten_tomatoes
.clone()
.unwrap_or_default()
.value
.as_u64()
.unwrap();
let imdb_rating = if imdb_rating == 0.0 {
String::default()
} else {
format!("{:.1}", imdb_rating)
};
let rotten_tomatoes_rating = if rotten_tomatoes_rating == 0 {
String::default()
} else {
format!("{}%", rotten_tomatoes_rating)
};
let in_library = if app
.data
.radarr_data
.movies
.items
.iter()
.any(|mov| mov.tmdb_id == movie.tmdb_id)
{
""
} else {
""
};
movie.title.scroll_left_or_reset(
get_width_from_percentage(area, 27),
*movie == current_selection,
);
movie.title.scroll_left_or_reset(
get_width_from_percentage(area, 27),
*movie == current_selection,
app.tick_count % app.ticks_until_scroll == 0,
);
Row::new(vec![
Cell::from(in_library),
Cell::from(movie.title.to_string()),
Cell::from(movie.year.as_u64().unwrap().to_string()),
Cell::from(format!("{}h {}m", hours, minutes)),
Cell::from(imdb_rating),
Cell::from(rotten_tomatoes_rating),
Cell::from(movie.genres.join(", ")),
])
.style(style_primary())
},
app.is_loading,
);
}
Row::new(vec![
Cell::from(in_library),
Cell::from(movie.title.to_string()),
Cell::from(movie.year.as_u64().unwrap().to_string()),
Cell::from(format!("{}h {}m", hours, minutes)),
Cell::from(imdb_rating),
Cell::from(rotten_tomatoes_rating),
Cell::from(movie.genres.join(", ")),
])
.style(style_primary())
},
app.is_loading,
);
}
_ => (),
}
+2 -1
View File
@@ -65,7 +65,7 @@ pub(super) fn draw_collection_details<B: Backend>(
.data
.radarr_data
.quality_profile_map
.get(&collection_selection.quality_profile_id.as_u64().unwrap())
.get_by_left(&collection_selection.quality_profile_id.as_u64().unwrap())
.unwrap()
.to_owned();
let current_selection = if app.data.radarr_data.collection_movies.items.is_empty() {
@@ -155,6 +155,7 @@ pub(super) fn draw_collection_details<B: Backend>(
movie.title.scroll_left_or_reset(
get_width_from_percentage(chunks[1], 20),
current_selection == *movie,
app.tick_count % app.ticks_until_scroll == 0,
);
let (hours, minutes) = convert_runtime(movie.runtime.as_u64().unwrap());
let imdb_rating = movie
+3 -2
View File
@@ -210,7 +210,7 @@ fn draw_library<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) {
let file_size: f64 = convert_to_gb(movie.size_on_disk.as_u64().unwrap());
let certification = movie.certification.clone().unwrap_or_else(|| "".to_owned());
let quality_profile = quality_profile_map
.get(&movie.quality_profile_id.as_u64().unwrap())
.get_by_left(&movie.quality_profile_id.as_u64().unwrap())
.unwrap()
.to_owned();
let tags = movie
@@ -475,6 +475,7 @@ fn draw_downloads<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) {
path.scroll_left_or_reset(
get_width_from_percentage(area, 18),
current_selection == *download_record,
app.tick_count % app.ticks_until_scroll == 0,
);
let percent = 1f64 - (sizeleft.as_f64().unwrap() / size.as_f64().unwrap());
@@ -533,7 +534,7 @@ fn draw_collections<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect)
Cell::from(collection.root_folder_path.clone().unwrap_or_default()),
Cell::from(
quality_profile_map
.get(&collection.quality_profile_id.as_u64().unwrap())
.get_by_left(&collection.quality_profile_id.as_u64().unwrap())
.unwrap()
.to_owned(),
),
+4 -2
View File
@@ -271,6 +271,7 @@ fn draw_movie_history<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, content_a
movie_history_item.source_title.scroll_left_or_reset(
get_width_from_percentage(content_area, 34),
current_selection == *movie_history_item,
app.tick_count % app.ticks_until_scroll == 0,
);
Row::new(vec![
@@ -322,7 +323,7 @@ fn draw_movie_cast<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, content_area
.style(style_success())
},
app.is_loading,
)
);
}
fn draw_movie_crew<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, content_area: Rect) {
@@ -442,6 +443,7 @@ fn draw_movie_releases<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, content_
get_width_from_percentage(content_area, 30),
current_selection == *release
&& current_route != ActiveRadarrBlock::ManualSearchConfirmPrompt.into(),
app.tick_count % app.ticks_until_scroll == 0,
);
let size = convert_to_gb(size.as_u64().unwrap());
let rejected_str = if *rejected { "" } else { "" };
@@ -477,7 +479,7 @@ fn draw_movie_releases<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, content_
.style(style_primary())
},
app.is_loading,
)
);
}
fn draw_manual_search_confirm_prompt<B: Backend>(