fix: dynamically load servarrs in UI based on what configs are provided

This commit is contained in:
2024-12-16 14:16:01 -07:00
parent 93cd235aef
commit e38e430c77
2 changed files with 113 additions and 12 deletions
+69 -6
View File
@@ -1,22 +1,81 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::models::Route;
use anyhow::anyhow; use anyhow::anyhow;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use crate::app::context_clues::{build_context_clue_string, SERVARR_CONTEXT_CLUES}; use crate::app::context_clues::{build_context_clue_string, SERVARR_CONTEXT_CLUES};
use crate::app::{App, AppConfig, Data, ServarrConfig, DEFAULT_ROUTE}; use crate::app::{App, AppConfig, Data, ServarrConfig};
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData}; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData};
use crate::models::{HorizontallyScrollableText, TabRoute}; use crate::models::{HorizontallyScrollableText, TabRoute};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::network::NetworkEvent; use crate::network::NetworkEvent;
use tokio_util::sync::CancellationToken;
#[rstest]
fn test_app_new(
#[values(ActiveRadarrBlock::default(), ActiveSonarrBlock::default())] servarr: impl Into<Route>
+ Copy,
) {
let (title, config) = match servarr.into() {
Route::Radarr(_, _) => (
"Radarr",
AppConfig {
radarr: Some(ServarrConfig::default()),
..AppConfig::default()
},
),
Route::Sonarr(_, _) => (
"Sonarr",
AppConfig {
sonarr: Some(ServarrConfig::default()),
..AppConfig::default()
},
),
_ => unreachable!(),
};
let tab_route = |title: &'static str| TabRoute {
title,
route: servarr.into(),
help: format!(
"<↑↓> scroll | ←→ change tab | {} ",
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
),
contextual_help: None,
};
let app = App::new(
mpsc::channel::<NetworkEvent>(500).0,
config,
CancellationToken::new(),
);
assert!(app.navigation_stack.is_empty());
assert_eq!(app.get_current_route(), servarr.into());
assert!(app.network_tx.is_some());
assert!(!app.cancellation_token.is_cancelled());
assert!(app.is_first_render);
assert_eq!(app.error, HorizontallyScrollableText::default());
assert_eq!(app.server_tabs.index, 0);
assert_eq!(app.server_tabs.tabs, vec![tab_route(title)]);
assert_eq!(app.tick_until_poll, 400);
assert_eq!(app.ticks_until_scroll, 4);
assert_eq!(app.tick_count, 0);
assert!(!app.is_loading);
assert!(!app.is_routing);
assert!(!app.should_refresh);
assert!(!app.should_ignore_quit_key);
assert!(!app.cli_mode);
}
#[test] #[test]
fn test_app_default() { fn test_app_default() {
let app = App::default(); let app = App::default();
assert_eq!(app.navigation_stack, vec![DEFAULT_ROUTE]); assert!(app.navigation_stack.is_empty());
assert!(app.network_tx.is_none()); assert!(app.network_tx.is_none());
assert!(!app.cancellation_token.is_cancelled()); assert!(!app.cancellation_token.is_cancelled());
assert!(app.is_first_render); assert!(app.is_first_render);
@@ -37,7 +96,10 @@ mod tests {
TabRoute { TabRoute {
title: "Sonarr", title: "Sonarr",
route: ActiveSonarrBlock::Series.into(), route: ActiveSonarrBlock::Series.into(),
help: format!("{} ", build_context_clue_string(&SERVARR_CONTEXT_CLUES)), help: format!(
"<↑↓> scroll | ←→ change tab | {} ",
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
),
contextual_help: None, contextual_help: None,
}, },
] ]
@@ -55,8 +117,9 @@ mod tests {
#[test] #[test]
fn test_navigation_stack_methods() { fn test_navigation_stack_methods() {
let mut app = App::default(); let mut app = App::default();
let default_route = app.server_tabs.tabs.first().unwrap().route;
assert_eq!(app.get_current_route(), DEFAULT_ROUTE); assert_eq!(app.get_current_route(), default_route);
app.push_navigation_stack(ActiveRadarrBlock::Downloads.into()); app.push_navigation_stack(ActiveRadarrBlock::Downloads.into());
@@ -75,13 +138,13 @@ mod tests {
app.is_routing = false; app.is_routing = false;
app.pop_navigation_stack(); app.pop_navigation_stack();
assert_eq!(app.get_current_route(), DEFAULT_ROUTE); assert_eq!(app.get_current_route(), default_route);
assert!(app.is_routing); assert!(app.is_routing);
app.is_routing = false; app.is_routing = false;
app.pop_navigation_stack(); app.pop_navigation_stack();
assert_eq!(app.get_current_route(), DEFAULT_ROUTE); assert_eq!(app.get_current_route(), default_route);
assert!(app.is_routing); assert!(app.is_routing);
} }
+44 -6
View File
@@ -23,8 +23,6 @@ mod key_binding_tests;
pub mod radarr; pub mod radarr;
pub mod sonarr; pub mod sonarr;
const DEFAULT_ROUTE: Route = Route::Radarr(ActiveRadarrBlock::Movies, None);
pub struct App<'a> { pub struct App<'a> {
navigation_stack: Vec<Route>, navigation_stack: Vec<Route>,
network_tx: Option<Sender<NetworkEvent>>, network_tx: Option<Sender<NetworkEvent>>,
@@ -50,10 +48,37 @@ impl<'a> App<'a> {
config: AppConfig, config: AppConfig,
cancellation_token: CancellationToken, cancellation_token: CancellationToken,
) -> Self { ) -> Self {
let mut server_tabs = Vec::new();
if config.radarr.is_some() {
server_tabs.push(TabRoute {
title: "Radarr",
route: ActiveRadarrBlock::Movies.into(),
help: format!(
"<↑↓> scroll | ←→ change tab | {} ",
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
),
contextual_help: None,
});
}
if config.sonarr.is_some() {
server_tabs.push(TabRoute {
title: "Sonarr",
route: ActiveSonarrBlock::Series.into(),
help: format!(
"<↑↓> scroll | ←→ change tab | {} ",
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
),
contextual_help: None,
});
}
App { App {
network_tx: Some(network_tx), network_tx: Some(network_tx),
config, config,
cancellation_token, cancellation_token,
server_tabs: TabState::new(server_tabs),
..App::default() ..App::default()
} }
} }
@@ -114,7 +139,7 @@ impl<'a> App<'a> {
pub fn pop_navigation_stack(&mut self) { pub fn pop_navigation_stack(&mut self) {
self.is_routing = true; self.is_routing = true;
if self.navigation_stack.len() > 1 { if !self.navigation_stack.is_empty() {
self.navigation_stack.pop(); self.navigation_stack.pop();
} }
} }
@@ -133,14 +158,17 @@ impl<'a> App<'a> {
} }
pub fn get_current_route(&self) -> Route { pub fn get_current_route(&self) -> Route {
*self.navigation_stack.last().unwrap_or(&DEFAULT_ROUTE) *self
.navigation_stack
.last()
.unwrap_or(&self.server_tabs.tabs.first().unwrap().route)
} }
} }
impl<'a> Default for App<'a> { impl<'a> Default for App<'a> {
fn default() -> Self { fn default() -> Self {
App { App {
navigation_stack: vec![DEFAULT_ROUTE], navigation_stack: Vec::new(),
network_tx: None, network_tx: None,
cancellation_token: CancellationToken::new(), cancellation_token: CancellationToken::new(),
error: HorizontallyScrollableText::default(), error: HorizontallyScrollableText::default(),
@@ -158,7 +186,10 @@ impl<'a> Default for App<'a> {
TabRoute { TabRoute {
title: "Sonarr", title: "Sonarr",
route: ActiveSonarrBlock::Series.into(), route: ActiveSonarrBlock::Series.into(),
help: format!("{} ", build_context_clue_string(&SERVARR_CONTEXT_CLUES)), help: format!(
"<↑↓> scroll | ←→ change tab | {} ",
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
),
contextual_help: None, contextual_help: None,
}, },
]), ]),
@@ -190,6 +221,13 @@ pub struct AppConfig {
impl AppConfig { impl AppConfig {
pub fn validate(&self) { pub fn validate(&self) {
if self.radarr.is_none() && self.sonarr.is_none() {
log_and_print_error(
"No Servarr configuration provided in the specified configuration file".to_owned(),
);
process::exit(1);
}
if let Some(radarr_config) = &self.radarr { if let Some(radarr_config) = &self.radarr {
radarr_config.validate(); radarr_config.validate();
} }