fix: dynamically load servarrs in UI based on what configs are provided
This commit is contained in:
+69
-6
@@ -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
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user