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
+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();