From a0073b65ad12cd18ffe301c0db00e07d6d6c9c84 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Thu, 4 Dec 2025 10:02:32 -0700 Subject: [PATCH] refactor: Added accessor methods to servarr_data structs, replaced for loops with functional iterator chains, eliminated mutable state tracking, and updated network module to use get_or_insert_default() for modal options --- src/app/mod.rs | 31 +++++----- src/models/mod.rs | 3 +- src/models/servarr_data/radarr/modals.rs | 50 ++-------------- src/models/servarr_data/radarr/radarr_data.rs | 18 ++++++ src/models/servarr_data/sonarr/modals.rs | 58 ++----------------- src/models/servarr_data/sonarr/sonarr_data.rs | 24 ++++++++ src/network/radarr_network/library/mod.rs | 40 +++---------- .../sonarr_network/library/series/mod.rs | 32 ++++------ src/ui/radarr_ui/library/mod.rs | 25 +++----- src/ui/sonarr_ui/library/mod.rs | 25 +++----- 10 files changed, 109 insertions(+), 197 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index eba8dec..894aac9 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -57,42 +57,43 @@ impl App<'_> { let mut server_tabs = Vec::new(); if let Some(radarr_configs) = config.radarr { - let mut idx = 0; - for radarr_config in radarr_configs { + let mut unnamed_idx = 0; + let radarr_tabs = radarr_configs.into_iter().map(|radarr_config| { let name = if let Some(name) = radarr_config.name.clone() { name } else { - idx += 1; - format!("Radarr {idx}") + unnamed_idx += 1; + format!("Radarr {unnamed_idx}") }; - server_tabs.push(TabRoute { + TabRoute { title: name, route: ActiveRadarrBlock::Movies.into(), contextual_help: None, config: Some(radarr_config), - }); - } + } + }); + server_tabs.extend(radarr_tabs); } if let Some(sonarr_configs) = config.sonarr { - let mut idx = 0; - - for sonarr_config in sonarr_configs { + let mut unnamed_idx = 0; + let sonarr_tabs = sonarr_configs.into_iter().map(|sonarr_config| { let name = if let Some(name) = sonarr_config.name.clone() { name } else { - idx += 1; - format!("Sonarr {idx}") + unnamed_idx += 1; + format!("Sonarr {unnamed_idx}") }; - server_tabs.push(TabRoute { + TabRoute { title: name, route: ActiveSonarrBlock::Series.into(), contextual_help: None, config: Some(sonarr_config), - }); - } + } + }); + server_tabs.extend(sonarr_tabs); } let weight_sorted_tabs = server_tabs diff --git a/src/models/mod.rs b/src/models/mod.rs index b0a2c33..74022de 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -65,8 +65,7 @@ pub struct ScrollableText { impl ScrollableText { pub fn with_string(item: String) -> ScrollableText { - let items: Vec<&str> = item.split('\n').collect(); - let items: Vec = items.iter().map(|it| it.to_string()).collect(); + let items: Vec = item.split('\n').map(str::to_owned).collect(); ScrollableText { items, offset: 0 } } diff --git a/src/models/servarr_data/radarr/modals.rs b/src/models/servarr_data/radarr/modals.rs index ca7b457..922757d 100644 --- a/src/models/servarr_data/radarr/modals.rs +++ b/src/models/servarr_data/radarr/modals.rs @@ -90,18 +90,7 @@ impl From<&RadarrData<'_>> for EditIndexerModal { .into(); } - edit_indexer_modal.tags = tags - .iter() - .map(|tag_id| { - radarr_data - .tags_map - .get_by_left(&tag_id.as_i64().expect("Tag ID must be a valid i64")) - .expect("Tag ID must exist in tags map") - .clone() - }) - .collect::>() - .join(", ") - .into(); + edit_indexer_modal.tags = radarr_data.tag_ids_to_display(tags).into(); edit_indexer_modal } @@ -132,18 +121,7 @@ impl From<&RadarrData<'_>> for EditMovieModal { .minimum_availability_list .set_items(Vec::from_iter(MinimumAvailability::iter())); edit_movie_modal.path = path.clone().into(); - edit_movie_modal.tags = tags - .iter() - .map(|tag_id| { - radarr_data - .tags_map - .get_by_left(&tag_id.as_i64().expect("Tag ID must be a valid i64")) - .expect("Tag ID must exist in tags map") - .clone() - }) - .collect::>() - .join(", ") - .into(); + edit_movie_modal.tags = radarr_data.tag_ids_to_display(tags).into(); edit_movie_modal.monitored = Some(*monitored); @@ -157,15 +135,9 @@ impl From<&RadarrData<'_>> for EditMovieModal { .state .select(minimum_availability_index); - let mut quality_profile_names: Vec = radarr_data - .quality_profile_map - .right_values() - .cloned() - .collect(); - quality_profile_names.sort(); edit_movie_modal .quality_profile_list - .set_items(quality_profile_names); + .set_items(radarr_data.sorted_quality_profile_names()); let quality_profile_name = radarr_data .quality_profile_map .get_by_left(quality_profile_id) @@ -202,15 +174,9 @@ impl From<&RadarrData<'_>> for AddMovieModal { add_movie_modal .minimum_availability_list .set_items(Vec::from_iter(MinimumAvailability::iter())); - let mut quality_profile_names: Vec = radarr_data - .quality_profile_map - .right_values() - .cloned() - .collect(); - quality_profile_names.sort(); add_movie_modal .quality_profile_list - .set_items(quality_profile_names); + .set_items(radarr_data.sorted_quality_profile_names()); add_movie_modal .root_folder_list .set_items(radarr_data.root_folders.items.to_vec()); @@ -246,15 +212,9 @@ impl From<&RadarrData<'_>> for EditCollectionModal { edit_collection_modal .minimum_availability_list .set_items(Vec::from_iter(MinimumAvailability::iter())); - let mut quality_profile_names: Vec = radarr_data - .quality_profile_map - .right_values() - .cloned() - .collect(); - quality_profile_names.sort(); edit_collection_modal .quality_profile_list - .set_items(quality_profile_names); + .set_items(radarr_data.sorted_quality_profile_names()); let minimum_availability_index = edit_collection_modal .minimum_availability_list diff --git a/src/models/servarr_data/radarr/radarr_data.rs b/src/models/servarr_data/radarr/radarr_data.rs index 1c43015..dad6b68 100644 --- a/src/models/servarr_data/radarr/radarr_data.rs +++ b/src/models/servarr_data/radarr/radarr_data.rs @@ -23,6 +23,7 @@ use crate::models::{ use crate::network::radarr_network::RadarrEvent; use bimap::BiMap; use chrono::{DateTime, Utc}; +use serde_json::Number; use strum::EnumIter; #[cfg(test)] @@ -81,6 +82,23 @@ impl RadarrData<'_> { self.movie_details_modal = None; self.movie_info_tabs.index = 0; } + + pub fn tag_ids_to_display(&self, tag_ids: &[Number]) -> String { + tag_ids + .iter() + .filter_map(|tag_id| { + let id = tag_id.as_i64()?; + self.tags_map.get_by_left(&id).cloned() + }) + .collect::>() + .join(", ") + } + + pub fn sorted_quality_profile_names(&self) -> Vec { + let mut names: Vec = self.quality_profile_map.right_values().cloned().collect(); + names.sort(); + names + } } impl<'a> Default for RadarrData<'a> { diff --git a/src/models/servarr_data/sonarr/modals.rs b/src/models/servarr_data/sonarr/modals.rs index df7f8e4..91f1dde 100644 --- a/src/models/servarr_data/sonarr/modals.rs +++ b/src/models/servarr_data/sonarr/modals.rs @@ -45,24 +45,12 @@ impl From<&SonarrData<'_>> for AddSeriesModal { add_series_modal .series_type_list .set_items(Vec::from_iter(SeriesType::iter())); - let mut quality_profile_names: Vec = sonarr_data - .quality_profile_map - .right_values() - .cloned() - .collect(); - quality_profile_names.sort(); add_series_modal .quality_profile_list - .set_items(quality_profile_names); - let mut language_profile_names: Vec = sonarr_data - .language_profiles_map - .right_values() - .cloned() - .collect(); - language_profile_names.sort(); + .set_items(sonarr_data.sorted_quality_profile_names()); add_series_modal .language_profile_list - .set_items(language_profile_names); + .set_items(sonarr_data.sorted_language_profile_names()); add_series_modal .root_folder_list .set_items(sonarr_data.root_folders.items.to_vec()); @@ -135,18 +123,7 @@ impl From<&SonarrData<'_>> for EditIndexerModal { .into(); } - edit_indexer_modal.tags = tags - .iter() - .map(|tag_id| { - sonarr_data - .tags_map - .get_by_left(&tag_id.as_i64().expect("Tag ID must be a valid i64")) - .expect("Tag ID must exist in tags map") - .clone() - }) - .collect::>() - .join(", ") - .into(); + edit_indexer_modal.tags = sonarr_data.tag_ids_to_display(tags).into(); edit_indexer_modal } @@ -181,18 +158,7 @@ impl From<&SonarrData<'_>> for EditSeriesModal { .series_type_list .set_items(Vec::from_iter(SeriesType::iter())); edit_series_modal.path = path.clone().into(); - edit_series_modal.tags = tags - .iter() - .map(|tag_id| { - sonarr_data - .tags_map - .get_by_left(&tag_id.as_i64().expect("Tag ID must be a valid i64")) - .expect("Tag ID must exist in tags map") - .clone() - }) - .collect::>() - .join(", ") - .into(); + edit_series_modal.tags = sonarr_data.tag_ids_to_display(tags).into(); edit_series_modal.monitored = Some(*monitored); edit_series_modal.use_season_folders = Some(*season_folder); @@ -207,15 +173,9 @@ impl From<&SonarrData<'_>> for EditSeriesModal { .state .select(series_type_index); - let mut quality_profile_names: Vec = sonarr_data - .quality_profile_map - .right_values() - .cloned() - .collect(); - quality_profile_names.sort(); edit_series_modal .quality_profile_list - .set_items(quality_profile_names); + .set_items(sonarr_data.sorted_quality_profile_names()); let quality_profile_name = sonarr_data .quality_profile_map .get_by_left(quality_profile_id) @@ -229,15 +189,9 @@ impl From<&SonarrData<'_>> for EditSeriesModal { .quality_profile_list .state .select(quality_profile_index); - let mut language_profile_names: Vec = sonarr_data - .language_profiles_map - .right_values() - .cloned() - .collect(); - language_profile_names.sort(); edit_series_modal .language_profile_list - .set_items(language_profile_names); + .set_items(sonarr_data.sorted_language_profile_names()); let language_profile_name = sonarr_data .language_profiles_map .get_by_left(language_profile_id) diff --git a/src/models/servarr_data/sonarr/sonarr_data.rs b/src/models/servarr_data/sonarr/sonarr_data.rs index 55d9456..8682088 100644 --- a/src/models/servarr_data/sonarr/sonarr_data.rs +++ b/src/models/servarr_data/sonarr/sonarr_data.rs @@ -1,5 +1,6 @@ use bimap::BiMap; use chrono::{DateTime, Utc}; +use serde_json::Number; use strum::EnumIter; use crate::{ @@ -87,6 +88,29 @@ impl SonarrData<'_> { self.seasons = StatefulTable::default(); self.series_info_tabs.index = 0; } + + pub fn tag_ids_to_display(&self, tag_ids: &[Number]) -> String { + tag_ids + .iter() + .filter_map(|tag_id| { + let id = tag_id.as_i64()?; + self.tags_map.get_by_left(&id).cloned() + }) + .collect::>() + .join(", ") + } + + pub fn sorted_quality_profile_names(&self) -> Vec { + let mut names: Vec = self.quality_profile_map.right_values().cloned().collect(); + names.sort(); + names + } + + pub fn sorted_language_profile_names(&self) -> Vec { + let mut names: Vec = self.language_profiles_map.right_values().cloned().collect(); + names.sort(); + names + } } impl<'a> Default for SonarrData<'a> { diff --git a/src/network/radarr_network/library/mod.rs b/src/network/radarr_network/library/mod.rs index f828069..6d08957 100644 --- a/src/network/radarr_network/library/mod.rs +++ b/src/network/radarr_network/library/mod.rs @@ -225,26 +225,14 @@ impl Network<'_, '_> { .cloned() .collect(); - if app.data.radarr_data.movie_details_modal.is_none() { - app.data.radarr_data.movie_details_modal = Some(MovieDetailsModal::default()); - } + let modal = app + .data + .radarr_data + .movie_details_modal + .get_or_insert_default(); - app - .data - .radarr_data - .movie_details_modal - .as_mut() - .unwrap() - .movie_cast - .set_items(cast_vec); - app - .data - .radarr_data - .movie_details_modal - .as_mut() - .unwrap() - .movie_crew - .set_items(crew_vec); + modal.movie_cast.set_items(cast_vec); + modal.movie_crew.set_items(crew_vec); }) .await } @@ -452,16 +440,11 @@ impl Network<'_, '_> { let mut reversed_movie_history_vec = movie_history_vec.to_vec(); reversed_movie_history_vec.reverse(); - if app.data.radarr_data.movie_details_modal.is_none() { - app.data.radarr_data.movie_details_modal = Some(MovieDetailsModal::default()) - } - app .data .radarr_data .movie_details_modal - .as_mut() - .unwrap() + .get_or_insert_default() .movie_history .set_items(reversed_movie_history_vec) }) @@ -487,16 +470,11 @@ impl Network<'_, '_> { self .handle_request::<(), Vec>(request_props, |release_vec, mut app| { - if app.data.radarr_data.movie_details_modal.is_none() { - app.data.radarr_data.movie_details_modal = Some(MovieDetailsModal::default()); - } - app .data .radarr_data .movie_details_modal - .as_mut() - .unwrap() + .get_or_insert_default() .movie_releases .set_items(release_vec); }) diff --git a/src/network/sonarr_network/library/series/mod.rs b/src/network/sonarr_network/library/series/mod.rs index 7e0bc7d..800c58d 100644 --- a/src/network/sonarr_network/library/series/mod.rs +++ b/src/network/sonarr_network/library/series/mod.rs @@ -307,29 +307,21 @@ impl Network<'_, '_> { self .handle_request::<(), Vec>(request_props, |mut history_vec, mut app| { - if app.data.sonarr_data.series_history.is_none() { - app.data.sonarr_data.series_history = Some(StatefulTable::default()); - } - - if !matches!( + let is_sorting = matches!( app.get_current_route(), Route::Sonarr(ActiveSonarrBlock::SeriesHistorySortPrompt, _) - ) { + ); + + let series_history = app + .data + .sonarr_data + .series_history + .get_or_insert_default(); + + if !is_sorting { history_vec.sort_by(|a, b| a.id.cmp(&b.id)); - app - .data - .sonarr_data - .series_history - .as_mut() - .unwrap() - .set_items(history_vec); - app - .data - .sonarr_data - .series_history - .as_mut() - .unwrap() - .apply_sorting_toggle(false); + series_history.set_items(history_vec); + series_history.apply_sorting_toggle(false); } }) .await diff --git a/src/ui/radarr_ui/library/mod.rs b/src/ui/radarr_ui/library/mod.rs index 7e86def..6e74e48 100644 --- a/src/ui/radarr_ui/library/mod.rs +++ b/src/ui/radarr_ui/library/mod.rs @@ -101,22 +101,15 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { .get_by_left(&movie.quality_profile_id) .expect("Quality profile ID must exist in quality_profile_map") .to_owned(); - let empty_tag = String::new(); - let tags = if !movie.tags.is_empty() { - movie - .tags - .iter() - .map(|tag_id| { - tags_map - .get_by_left(&tag_id.as_i64().expect("Tag ID must be a valid i64")) - .unwrap_or(&empty_tag) - .clone() - }) - .collect::>() - .join(", ") - } else { - String::new() - }; + let tags = movie + .tags + .iter() + .filter_map(|tag_id| { + let id = tag_id.as_i64()?; + tags_map.get_by_left(&id).cloned() + }) + .collect::>() + .join(", "); decorate_with_row_style( downloads_vec, diff --git a/src/ui/sonarr_ui/library/mod.rs b/src/ui/sonarr_ui/library/mod.rs index ba3c594..0d91894 100644 --- a/src/ui/sonarr_ui/library/mod.rs +++ b/src/ui/sonarr_ui/library/mod.rs @@ -112,22 +112,15 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { .get_by_left(&series.language_profile_id) .expect("Language profile ID must exist in language_profile_map") .to_owned(); - let empty_tag = String::new(); - let tags = if !series.tags.is_empty() { - series - .tags - .iter() - .map(|tag_id| { - tags_map - .get_by_left(&tag_id.as_i64().expect("Tag ID must be a valid i64")) - .unwrap_or(&empty_tag) - .clone() - }) - .collect::>() - .join(", ") - } else { - String::new() - }; + let tags = series + .tags + .iter() + .filter_map(|tag_id| { + let id = tag_id.as_i64()?; + tags_map.get_by_left(&id).cloned() + }) + .collect::>() + .join(", "); decorate_series_row_with_style( series,