Full support for editing movies and managing tags
This commit is contained in:
+1
-1
@@ -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"]
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
|
||||
@@ -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>(
|
||||
|
||||
Reference in New Issue
Block a user