feat: scaffold Readarr support across the state, app, handler, UI, and CLI layers
Promote Route::Readarr from a unit stub to a tuple variant and wire Readarr through every layer, mirroring the Lidarr tree. Readarr appears as a fourth servarr tab and its library view renders the Authors table. Adds ReadarrData and its modals, the tick and context clue modules, the top-level ReadarrHandler, ReadarrUi with the Authors library view, and an empty ReadarrCommand behind ReadarrCliHandler. The default port is 8787. Promoting the route variant compiles cleanly without any of the arms that give it behaviour, so all six of those sites are wired explicitly here: App::on_tick, handlers::handle_events, handle_prompt_toggle, ServarrContextClueProvider, ui(), and verify_config_present_for_cli. Each was checked by removing the arm and confirming its covering assertion then fails. ReadarrEvent and ReadarrCommand are both declared empty. They gain variants alongside the network calls and CLI commands that use them, which keeps each later change to a single function. Adding the tab changes the rendered tab bar, so 13 existing snapshots regenerate. The only delta in each is that single tab bar line. This scaffold adds 24 dead_code warnings, all inside the Readarr tree and none outside it. They clear as the handlers, UI, network events, and CLI commands that consume these items land: block arrays awaiting their accepts implementations and BlockSelectionState seeding, modal and state fields awaiting their handlers, the CLI handler fields awaiting an inhabited command enum, and the top-level handler fields awaiting sub-handlers. The count must strictly decrease from here, so a later increase is a defect rather than transient noise. The 12 pre-existing collapsible_match warnings are unrelated and untouched.
This commit is contained in:
64 files changed
+6418
-22
No files matched your search
@@ -11,6 +11,7 @@ mod tests {
|
||||
use crate::app::{App, AppConfig, Data, ServarrConfig, interpolate_env_vars};
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::LidarrData;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
|
||||
use crate::models::servarr_data::readarr::readarr_data::{ActiveReadarrBlock, ReadarrData};
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData};
|
||||
use crate::models::{HorizontallyScrollableText, TabRoute};
|
||||
use crate::network::NetworkEvent;
|
||||
@@ -33,11 +34,18 @@ mod tests {
|
||||
..ServarrConfig::default()
|
||||
};
|
||||
let sonarr_config_2 = ServarrConfig::default();
|
||||
let readarr_config_1 = ServarrConfig {
|
||||
name: Some("Readarr Test".to_owned()),
|
||||
weight: Some(2),
|
||||
..ServarrConfig::default()
|
||||
};
|
||||
let readarr_config_2 = ServarrConfig::default();
|
||||
let config = AppConfig {
|
||||
theme: None,
|
||||
radarr: Some(vec![radarr_config_1.clone(), radarr_config_2.clone()]),
|
||||
sonarr: Some(vec![sonarr_config_1.clone(), sonarr_config_2.clone()]),
|
||||
lidarr: None,
|
||||
readarr: Some(vec![readarr_config_1.clone(), readarr_config_2.clone()]),
|
||||
};
|
||||
let expected_tab_routes = vec![
|
||||
TabRoute {
|
||||
@@ -46,6 +54,12 @@ mod tests {
|
||||
contextual_help: None,
|
||||
config: Some(sonarr_config_1),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Readarr Test".to_owned(),
|
||||
route: ActiveReadarrBlock::default().into(),
|
||||
contextual_help: None,
|
||||
config: Some(readarr_config_1),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Radarr 1".to_owned(),
|
||||
route: ActiveRadarrBlock::default().into(),
|
||||
@@ -64,6 +78,12 @@ mod tests {
|
||||
contextual_help: None,
|
||||
config: Some(sonarr_config_2),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Readarr 1".to_owned(),
|
||||
route: ActiveReadarrBlock::default().into(),
|
||||
contextual_help: None,
|
||||
config: Some(readarr_config_2),
|
||||
},
|
||||
];
|
||||
|
||||
let app = App::new(
|
||||
@@ -189,6 +209,7 @@ mod tests {
|
||||
let data = Data {
|
||||
lidarr_data: LidarrData::default(),
|
||||
radarr_data,
|
||||
readarr_data: ReadarrData::default(),
|
||||
sonarr_data,
|
||||
};
|
||||
let mut app = App {
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::app::App;
|
||||
use crate::app::key_binding::{DEFAULT_KEYBINDINGS, KeyBinding};
|
||||
use crate::app::lidarr::lidarr_context_clues::LidarrContextClueProvider;
|
||||
use crate::app::radarr::radarr_context_clues::RadarrContextClueProvider;
|
||||
use crate::app::readarr::readarr_context_clues::ReadarrContextClueProvider;
|
||||
use crate::app::sonarr::sonarr_context_clues::SonarrContextClueProvider;
|
||||
use crate::models::Route;
|
||||
|
||||
@@ -23,6 +24,7 @@ impl ContextClueProvider for ServarrContextClueProvider {
|
||||
Route::Radarr(_, _) => RadarrContextClueProvider::get_context_clues(app),
|
||||
Route::Sonarr(_, _) => SonarrContextClueProvider::get_context_clues(app),
|
||||
Route::Lidarr(_, _) => LidarrContextClueProvider::get_context_clues(app),
|
||||
Route::Readarr(_, _) => ReadarrContextClueProvider::get_context_clues(app),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ mod test {
|
||||
use crate::app::{App, key_binding::DEFAULT_KEYBINDINGS};
|
||||
use crate::models::servarr_data::ActiveKeybindingBlock;
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
|
||||
|
||||
#[test]
|
||||
@@ -303,6 +304,16 @@ mod test {
|
||||
assert_some_eq_x!(context_clues, &SYSTEM_TASKS_CONTEXT_CLUES,);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_servarr_context_clue_provider_delegates_to_readarr_provider() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveReadarrBlock::SystemTasks.into());
|
||||
|
||||
let context_clues = ServarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &SYSTEM_TASKS_CONTEXT_CLUES,);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_servarr_context_clue_provider_unsupported_route_returns_none() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
+57
-1
@@ -17,6 +17,7 @@ use crate::cli::Command;
|
||||
use crate::models::servarr_data::Notification;
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, LidarrData};
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
|
||||
use crate::models::servarr_data::readarr::readarr_data::{ActiveReadarrBlock, ReadarrData};
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData};
|
||||
use crate::models::servarr_models::KeybindingItem;
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
@@ -30,6 +31,7 @@ pub mod key_binding;
|
||||
mod key_binding_tests;
|
||||
pub mod lidarr;
|
||||
pub mod radarr;
|
||||
pub mod readarr;
|
||||
pub mod sonarr;
|
||||
|
||||
pub struct App<'a> {
|
||||
@@ -122,6 +124,26 @@ impl App<'_> {
|
||||
server_tabs.extend(lidarr_tabs);
|
||||
}
|
||||
|
||||
if let Some(readarr_configs) = config.readarr {
|
||||
let mut unnamed_idx = 0;
|
||||
let readarr_tabs = readarr_configs.into_iter().map(|readarr_config| {
|
||||
let name = if let Some(name) = readarr_config.name.clone() {
|
||||
name
|
||||
} else {
|
||||
unnamed_idx += 1;
|
||||
format!("Readarr {unnamed_idx}")
|
||||
};
|
||||
|
||||
TabRoute {
|
||||
title: name,
|
||||
route: ActiveReadarrBlock::Authors.into(),
|
||||
contextual_help: None,
|
||||
config: Some(readarr_config),
|
||||
}
|
||||
});
|
||||
server_tabs.extend(readarr_tabs);
|
||||
}
|
||||
|
||||
let weight_sorted_tabs = server_tabs
|
||||
.into_iter()
|
||||
.sorted_by(|tab1, tab2| {
|
||||
@@ -204,6 +226,7 @@ impl App<'_> {
|
||||
Route::Radarr(active_radarr_block, _) => self.radarr_on_tick(active_radarr_block).await,
|
||||
Route::Sonarr(active_sonarr_block, _) => self.sonarr_on_tick(active_sonarr_block).await,
|
||||
Route::Lidarr(active_lidarr_block, _) => self.lidarr_on_tick(active_lidarr_block).await,
|
||||
Route::Readarr(active_readarr_block, _) => self.readarr_on_tick(active_readarr_block).await,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@@ -300,6 +323,12 @@ impl App<'_> {
|
||||
contextual_help: None,
|
||||
config: Some(ServarrConfig::default()),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Readarr".to_owned(),
|
||||
route: ActiveReadarrBlock::Authors.into(),
|
||||
contextual_help: None,
|
||||
config: Some(ServarrConfig::default()),
|
||||
},
|
||||
]),
|
||||
..App::default()
|
||||
}
|
||||
@@ -310,6 +339,7 @@ impl App<'_> {
|
||||
data: Data {
|
||||
lidarr_data: LidarrData::test_default_fully_populated(),
|
||||
radarr_data: RadarrData::test_default_fully_populated(),
|
||||
readarr_data: ReadarrData::test_default_fully_populated(),
|
||||
sonarr_data: SonarrData::test_default_fully_populated(),
|
||||
},
|
||||
server_tabs: TabState::new(vec![
|
||||
@@ -331,6 +361,12 @@ impl App<'_> {
|
||||
contextual_help: None,
|
||||
config: Some(ServarrConfig::default()),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Readarr".to_owned(),
|
||||
route: ActiveReadarrBlock::Authors.into(),
|
||||
contextual_help: None,
|
||||
config: Some(ServarrConfig::default()),
|
||||
},
|
||||
]),
|
||||
..App::default()
|
||||
}
|
||||
@@ -341,6 +377,7 @@ impl App<'_> {
|
||||
pub struct Data<'a> {
|
||||
pub lidarr_data: LidarrData<'a>,
|
||||
pub radarr_data: RadarrData<'a>,
|
||||
pub readarr_data: ReadarrData<'a>,
|
||||
pub sonarr_data: SonarrData<'a>,
|
||||
}
|
||||
|
||||
@@ -349,12 +386,17 @@ pub struct AppConfig {
|
||||
pub theme: Option<String>,
|
||||
pub lidarr: Option<Vec<ServarrConfig>>,
|
||||
pub radarr: Option<Vec<ServarrConfig>>,
|
||||
pub readarr: Option<Vec<ServarrConfig>>,
|
||||
pub sonarr: Option<Vec<ServarrConfig>>,
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
pub fn validate(&self, config_path: &str) {
|
||||
if self.lidarr.is_none() && self.radarr.is_none() && self.sonarr.is_none() {
|
||||
if self.lidarr.is_none()
|
||||
&& self.radarr.is_none()
|
||||
&& self.sonarr.is_none()
|
||||
&& self.readarr.is_none()
|
||||
{
|
||||
log_and_print_error(format!(
|
||||
"No Servarrs are configured in the file: {config_path}"
|
||||
));
|
||||
@@ -372,6 +414,10 @@ impl AppConfig {
|
||||
if let Some(lidarr_configs) = &self.lidarr {
|
||||
lidarr_configs.iter().for_each(|config| config.validate());
|
||||
}
|
||||
|
||||
if let Some(readarr_configs) = &self.readarr {
|
||||
readarr_configs.iter().for_each(|config| config.validate());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_config_present_for_cli(&self, command: &Command) {
|
||||
@@ -393,6 +439,10 @@ impl AppConfig {
|
||||
msg("Lidarr");
|
||||
process::exit(1);
|
||||
}
|
||||
Command::Readarr(_) if self.readarr.is_none() => {
|
||||
msg("Readarr");
|
||||
process::exit(1);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -415,6 +465,12 @@ impl AppConfig {
|
||||
lidarr_config.post_process_initialization();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(readarr_configs) = self.readarr.as_mut() {
|
||||
for readarr_config in readarr_configs {
|
||||
readarr_config.post_process_initialization();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
use super::App;
|
||||
use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock;
|
||||
|
||||
pub mod readarr_context_clues;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "readarr_tests.rs"]
|
||||
mod readarr_tests;
|
||||
|
||||
impl App<'_> {
|
||||
pub(super) async fn dispatch_by_readarr_block(
|
||||
&mut self,
|
||||
_active_readarr_block: &ActiveReadarrBlock,
|
||||
) {
|
||||
self.check_for_readarr_prompt_action().await;
|
||||
self.reset_tick_count();
|
||||
}
|
||||
|
||||
async fn check_for_readarr_prompt_action(&mut self) {
|
||||
if self.data.readarr_data.prompt_confirm {
|
||||
self.data.readarr_data.prompt_confirm = false;
|
||||
if let Some(readarr_event) = self.data.readarr_data.prompt_confirm_action.take() {
|
||||
self.dispatch_network_event(readarr_event.into()).await;
|
||||
self.should_refresh = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn readarr_on_tick(&mut self, active_readarr_block: ActiveReadarrBlock) {
|
||||
if self.is_first_render {
|
||||
self.dispatch_by_readarr_block(&active_readarr_block).await;
|
||||
self.is_first_render = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if self.should_refresh {
|
||||
self.dispatch_by_readarr_block(&active_readarr_block).await;
|
||||
}
|
||||
|
||||
if self.is_routing {
|
||||
if !self.should_refresh {
|
||||
self.cancellation_token.cancel();
|
||||
} else {
|
||||
self.dispatch_by_readarr_block(&active_readarr_block).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
use crate::app::App;
|
||||
use crate::app::context_clues::{
|
||||
BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClue, ContextClueProvider,
|
||||
SYSTEM_TASKS_CONTEXT_CLUES,
|
||||
};
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::readarr::readarr_data::{
|
||||
ADD_AUTHOR_BLOCKS, ADD_ROOT_FOLDER_BLOCKS, AUTHOR_DETAILS_BLOCKS, ActiveReadarrBlock,
|
||||
BOOK_DETAILS_BLOCKS, EDIT_AUTHOR_BLOCKS, EDIT_INDEXER_BLOCKS, EDITION_DETAILS_BLOCKS,
|
||||
INDEXER_SETTINGS_BLOCKS,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "readarr_context_clues_tests.rs"]
|
||||
mod readarr_context_clues_tests;
|
||||
|
||||
pub static AUTHORS_CONTEXT_CLUES: [ContextClue; 10] = [
|
||||
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
|
||||
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
||||
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.update, "update all"),
|
||||
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
|
||||
];
|
||||
|
||||
pub static ADD_AUTHOR_SEARCH_RESULTS_CONTEXT_CLUES: [ContextClue; 2] = [
|
||||
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||
(DEFAULT_KEYBINDINGS.esc, "edit search"),
|
||||
];
|
||||
|
||||
pub static AUTHOR_DETAILS_CONTEXT_CLUES: [ContextClue; 8] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.edit, "edit author"),
|
||||
(DEFAULT_KEYBINDINGS.delete, "delete book"),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||
"toggle book monitoring",
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||
(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||
];
|
||||
|
||||
pub static AUTHOR_HISTORY_CONTEXT_CLUES: [ContextClue; 9] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.edit, "edit author"),
|
||||
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||
(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc),
|
||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.esc, "cancel filter/close"),
|
||||
];
|
||||
|
||||
pub static MANUAL_AUTHOR_SEARCH_CONTEXT_CLUES: [ContextClue; 7] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.edit, "edit author"),
|
||||
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||
(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||
];
|
||||
|
||||
pub static BOOK_DETAILS_CONTEXT_CLUES: [ContextClue; 6] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||
(DEFAULT_KEYBINDINGS.submit, "edition details"),
|
||||
(DEFAULT_KEYBINDINGS.delete, "delete book file"),
|
||||
];
|
||||
|
||||
pub static BOOK_HISTORY_CONTEXT_CLUES: [ContextClue; 7] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||
(DEFAULT_KEYBINDINGS.esc, "cancel filter/close"),
|
||||
];
|
||||
|
||||
pub static MANUAL_BOOK_SEARCH_CONTEXT_CLUES: [ContextClue; 5] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||
];
|
||||
|
||||
pub static EDITION_DETAILS_CONTEXT_CLUES: [ContextClue; 2] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||
];
|
||||
|
||||
pub(in crate::app) struct ReadarrContextClueProvider;
|
||||
|
||||
impl ContextClueProvider for ReadarrContextClueProvider {
|
||||
fn get_context_clues(app: &mut App<'_>) -> Option<&'static [ContextClue]> {
|
||||
let Route::Readarr(active_readarr_block, _context_option) = app.get_current_route() else {
|
||||
panic!("ReadarrContextClueProvider::get_context_clues called with non-Readarr route");
|
||||
};
|
||||
|
||||
match active_readarr_block {
|
||||
_ if AUTHOR_DETAILS_BLOCKS.contains(&active_readarr_block) => app
|
||||
.data
|
||||
.readarr_data
|
||||
.author_info_tabs
|
||||
.get_active_route_contextual_help(),
|
||||
_ if BOOK_DETAILS_BLOCKS.contains(&active_readarr_block) => app
|
||||
.data
|
||||
.readarr_data
|
||||
.book_details_modal
|
||||
.as_ref()
|
||||
.expect("book_details_modal is empty")
|
||||
.book_details_tabs
|
||||
.get_active_route_contextual_help(),
|
||||
_ if EDITION_DETAILS_BLOCKS.contains(&active_readarr_block) => {
|
||||
Some(&EDITION_DETAILS_CONTEXT_CLUES)
|
||||
}
|
||||
ActiveReadarrBlock::AddAuthorSearchInput
|
||||
| ActiveReadarrBlock::AddAuthorEmptySearchResults
|
||||
| ActiveReadarrBlock::TestAllIndexers
|
||||
| ActiveReadarrBlock::SystemLogs
|
||||
| ActiveReadarrBlock::SystemUpdates => Some(&BARE_POPUP_CONTEXT_CLUES),
|
||||
_ if EDIT_AUTHOR_BLOCKS.contains(&active_readarr_block)
|
||||
|| EDIT_INDEXER_BLOCKS.contains(&active_readarr_block)
|
||||
|| INDEXER_SETTINGS_BLOCKS.contains(&active_readarr_block)
|
||||
|| ADD_ROOT_FOLDER_BLOCKS.contains(&active_readarr_block) =>
|
||||
{
|
||||
Some(&CONFIRMATION_PROMPT_CONTEXT_CLUES)
|
||||
}
|
||||
ActiveReadarrBlock::AddAuthorPrompt
|
||||
| ActiveReadarrBlock::AddAuthorSelectMonitor
|
||||
| ActiveReadarrBlock::AddAuthorSelectMonitorNewItems
|
||||
| ActiveReadarrBlock::AddAuthorSelectQualityProfile
|
||||
| ActiveReadarrBlock::AddAuthorSelectMetadataProfile
|
||||
| ActiveReadarrBlock::AddAuthorSelectRootFolder
|
||||
| ActiveReadarrBlock::AddAuthorTagsInput
|
||||
| ActiveReadarrBlock::AddAuthorAlreadyInLibrary => Some(&CONFIRMATION_PROMPT_CONTEXT_CLUES),
|
||||
_ if ADD_AUTHOR_BLOCKS.contains(&active_readarr_block) => {
|
||||
Some(&ADD_AUTHOR_SEARCH_RESULTS_CONTEXT_CLUES)
|
||||
}
|
||||
ActiveReadarrBlock::SystemTasks => Some(&SYSTEM_TASKS_CONTEXT_CLUES),
|
||||
_ => app
|
||||
.data
|
||||
.readarr_data
|
||||
.main_tabs
|
||||
.get_active_route_contextual_help(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,540 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::app::App;
|
||||
use crate::app::context_clues::{
|
||||
BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClue, ContextClueProvider,
|
||||
SYSTEM_TASKS_CONTEXT_CLUES,
|
||||
};
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::readarr::readarr_context_clues::{
|
||||
ADD_AUTHOR_SEARCH_RESULTS_CONTEXT_CLUES, AUTHOR_DETAILS_CONTEXT_CLUES,
|
||||
AUTHOR_HISTORY_CONTEXT_CLUES, AUTHORS_CONTEXT_CLUES, BOOK_DETAILS_CONTEXT_CLUES,
|
||||
BOOK_HISTORY_CONTEXT_CLUES, EDITION_DETAILS_CONTEXT_CLUES, MANUAL_AUTHOR_SEARCH_CONTEXT_CLUES,
|
||||
MANUAL_BOOK_SEARCH_CONTEXT_CLUES, ReadarrContextClueProvider,
|
||||
};
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
use crate::models::servarr_data::readarr::modals::BookDetailsModal;
|
||||
use crate::models::servarr_data::readarr::readarr_data::{
|
||||
ADD_ROOT_FOLDER_BLOCKS, ActiveReadarrBlock, EDIT_AUTHOR_BLOCKS, EDIT_INDEXER_BLOCKS,
|
||||
INDEXER_SETTINGS_BLOCKS, ReadarrData,
|
||||
};
|
||||
use rstest::rstest;
|
||||
|
||||
#[test]
|
||||
fn test_authors_context_clues() {
|
||||
let mut authors_context_clues_iter = AUTHORS_CONTEXT_CLUES.iter();
|
||||
|
||||
assert_some_eq_x!(
|
||||
authors_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
authors_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
authors_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
authors_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
authors_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
authors_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
authors_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
authors_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
authors_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.update, "update all")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
authors_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, "cancel filter")
|
||||
);
|
||||
assert_none!(authors_context_clues_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_author_details_context_clues() {
|
||||
let mut author_details_context_clues_iter = AUTHOR_DETAILS_CONTEXT_CLUES.iter();
|
||||
|
||||
assert_some_eq_x!(
|
||||
author_details_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
author_details_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.edit, "edit author")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
author_details_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.delete, "delete book")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
author_details_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||
"toggle book monitoring",
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
author_details_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
author_details_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
author_details_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
author_details_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||
);
|
||||
assert_none!(author_details_context_clues_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_author_search_results_context_clues() {
|
||||
let mut add_author_search_results_context_clues_iter =
|
||||
ADD_AUTHOR_SEARCH_RESULTS_CONTEXT_CLUES.iter();
|
||||
|
||||
assert_some_eq_x!(
|
||||
add_author_search_results_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
add_author_search_results_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, "edit search")
|
||||
);
|
||||
assert_none!(add_author_search_results_context_clues_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "ReadarrContextClueProvider::get_context_clues called with non-Readarr route"
|
||||
)]
|
||||
fn test_readarr_context_clue_provider_get_context_clues_non_readarr_route() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::default().into());
|
||||
|
||||
ReadarrContextClueProvider::get_context_clues(&mut app);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_author_history_context_clues() {
|
||||
let mut author_history_context_clues_iter = AUTHOR_HISTORY_CONTEXT_CLUES.iter();
|
||||
|
||||
assert_some_eq_x!(
|
||||
author_history_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
author_history_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.edit, "edit author")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
author_history_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
author_history_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
author_history_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
author_history_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
author_history_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
author_history_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
author_history_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, "cancel filter/close")
|
||||
);
|
||||
assert_none!(author_history_context_clues_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_manual_author_search_context_clues() {
|
||||
let mut manual_author_search_context_clues_iter = MANUAL_AUTHOR_SEARCH_CONTEXT_CLUES.iter();
|
||||
|
||||
assert_some_eq_x!(
|
||||
manual_author_search_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
manual_author_search_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.edit, "edit author")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
manual_author_search_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
manual_author_search_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
manual_author_search_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
manual_author_search_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
manual_author_search_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||
);
|
||||
assert_none!(manual_author_search_context_clues_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_book_details_context_clues() {
|
||||
let mut book_details_context_clues_iter = BOOK_DETAILS_CONTEXT_CLUES.iter();
|
||||
|
||||
assert_some_eq_x!(
|
||||
book_details_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
book_details_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
book_details_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
book_details_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
book_details_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.submit, "edition details")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
book_details_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.delete, "delete book file")
|
||||
);
|
||||
assert_none!(book_details_context_clues_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_book_history_context_clues() {
|
||||
let mut book_history_context_clues_iter = BOOK_HISTORY_CONTEXT_CLUES.iter();
|
||||
|
||||
assert_some_eq_x!(
|
||||
book_history_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
book_history_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
book_history_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
book_history_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
book_history_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
book_history_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
book_history_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, "cancel filter/close")
|
||||
);
|
||||
assert_none!(book_history_context_clues_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_manual_book_search_context_clues() {
|
||||
let mut manual_book_search_context_clues_iter = MANUAL_BOOK_SEARCH_CONTEXT_CLUES.iter();
|
||||
|
||||
assert_some_eq_x!(
|
||||
manual_book_search_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
manual_book_search_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
manual_book_search_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
manual_book_search_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
manual_book_search_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||
);
|
||||
assert_none!(manual_book_search_context_clues_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edition_details_context_clues() {
|
||||
let mut edition_details_context_clues_iter = EDITION_DETAILS_CONTEXT_CLUES.iter();
|
||||
|
||||
assert_some_eq_x!(
|
||||
edition_details_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
edition_details_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||
);
|
||||
assert_none!(edition_details_context_clues_iter.next());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(0, ActiveReadarrBlock::AuthorDetails, &AUTHOR_DETAILS_CONTEXT_CLUES)]
|
||||
#[case(1, ActiveReadarrBlock::AuthorHistory, &AUTHOR_HISTORY_CONTEXT_CLUES)]
|
||||
#[case(2, ActiveReadarrBlock::ManualAuthorSearch, &MANUAL_AUTHOR_SEARCH_CONTEXT_CLUES)]
|
||||
fn test_readarr_context_clue_provider_author_info_tabs(
|
||||
#[case] index: usize,
|
||||
#[case] active_readarr_block: ActiveReadarrBlock,
|
||||
#[case] expected_context_clues: &[ContextClue],
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.data.readarr_data = ReadarrData::default();
|
||||
app.data.readarr_data.author_info_tabs.set_index(index);
|
||||
app.push_navigation_stack(active_readarr_block.into());
|
||||
|
||||
let context_clues = ReadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, expected_context_clues);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(0, ActiveReadarrBlock::BookDetails, &BOOK_DETAILS_CONTEXT_CLUES)]
|
||||
#[case(1, ActiveReadarrBlock::BookHistory, &BOOK_HISTORY_CONTEXT_CLUES)]
|
||||
#[case(2, ActiveReadarrBlock::ManualBookSearch, &MANUAL_BOOK_SEARCH_CONTEXT_CLUES)]
|
||||
fn test_readarr_context_clue_provider_book_details_tabs(
|
||||
#[case] index: usize,
|
||||
#[case] active_readarr_block: ActiveReadarrBlock,
|
||||
#[case] expected_context_clues: &[ContextClue],
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
let mut book_details_modal = BookDetailsModal::default();
|
||||
book_details_modal.book_details_tabs.set_index(index);
|
||||
let readarr_data = ReadarrData {
|
||||
book_details_modal: Some(book_details_modal),
|
||||
..ReadarrData::default()
|
||||
};
|
||||
app.data.readarr_data = readarr_data;
|
||||
app.push_navigation_stack(active_readarr_block.into());
|
||||
|
||||
let context_clues = ReadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, expected_context_clues);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readarr_context_clue_provider_book_edition_details_block() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveReadarrBlock::BookEditionDetails.into());
|
||||
|
||||
let context_clues = ReadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &EDITION_DETAILS_CONTEXT_CLUES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readarr_context_clue_provider_authors_block() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveReadarrBlock::Authors.into());
|
||||
|
||||
let context_clues = ReadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &AUTHORS_CONTEXT_CLUES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readarr_context_clue_provider_authors_sort_prompt_block() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveReadarrBlock::AuthorsSortPrompt.into());
|
||||
|
||||
let context_clues = ReadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &AUTHORS_CONTEXT_CLUES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readarr_context_clue_provider_search_authors_block() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveReadarrBlock::SearchAuthors.into());
|
||||
|
||||
let context_clues = ReadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &AUTHORS_CONTEXT_CLUES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readarr_context_clue_provider_filter_authors_block() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveReadarrBlock::FilterAuthors.into());
|
||||
|
||||
let context_clues = ReadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &AUTHORS_CONTEXT_CLUES);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_readarr_context_clue_provider_bare_popup_context_clues(
|
||||
#[values(
|
||||
ActiveReadarrBlock::AddAuthorSearchInput,
|
||||
ActiveReadarrBlock::AddAuthorEmptySearchResults,
|
||||
ActiveReadarrBlock::TestAllIndexers,
|
||||
ActiveReadarrBlock::SystemLogs,
|
||||
ActiveReadarrBlock::SystemUpdates
|
||||
)]
|
||||
active_readarr_block: ActiveReadarrBlock,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(active_readarr_block.into());
|
||||
|
||||
let context_clues = ReadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &BARE_POPUP_CONTEXT_CLUES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readarr_context_clue_provider_confirmation_prompt_popup_clues_edit_indexer_blocks() {
|
||||
let mut blocks = EDIT_AUTHOR_BLOCKS.to_vec();
|
||||
blocks.extend(ADD_ROOT_FOLDER_BLOCKS);
|
||||
blocks.extend(INDEXER_SETTINGS_BLOCKS);
|
||||
blocks.extend(EDIT_INDEXER_BLOCKS);
|
||||
|
||||
for active_readarr_block in blocks {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(active_readarr_block.into());
|
||||
|
||||
let context_clues = ReadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readarr_context_clue_provider_add_author_search_results_context_clues() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveReadarrBlock::AddAuthorSearchResults.into());
|
||||
|
||||
let context_clues = ReadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &ADD_AUTHOR_SEARCH_RESULTS_CONTEXT_CLUES);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_readarr_context_clue_provider_confirmation_prompt_context_clues_add_author_blocks(
|
||||
#[values(
|
||||
ActiveReadarrBlock::AddAuthorPrompt,
|
||||
ActiveReadarrBlock::AddAuthorSelectMonitor,
|
||||
ActiveReadarrBlock::AddAuthorSelectMonitorNewItems,
|
||||
ActiveReadarrBlock::AddAuthorSelectQualityProfile,
|
||||
ActiveReadarrBlock::AddAuthorSelectMetadataProfile,
|
||||
ActiveReadarrBlock::AddAuthorSelectRootFolder,
|
||||
ActiveReadarrBlock::AddAuthorTagsInput,
|
||||
ActiveReadarrBlock::AddAuthorAlreadyInLibrary
|
||||
)]
|
||||
active_readarr_block: ActiveReadarrBlock,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(active_readarr_block.into());
|
||||
|
||||
let context_clues = ReadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readarr_context_clue_provider_system_tasks_clues() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
app.push_navigation_stack(ActiveReadarrBlock::SystemTasks.into());
|
||||
let context_clues = ReadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &SYSTEM_TASKS_CONTEXT_CLUES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::app::App;
|
||||
use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_dispatch_by_readarr_block_resets_the_tick_count() {
|
||||
let mut app = App {
|
||||
tick_count: 2,
|
||||
..App::test_default()
|
||||
};
|
||||
|
||||
app
|
||||
.dispatch_by_readarr_block(&ActiveReadarrBlock::Authors)
|
||||
.await;
|
||||
|
||||
assert_eq!(app.tick_count, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_dispatch_by_readarr_block_clears_the_pending_prompt_confirmation() {
|
||||
let mut app = App::test_default();
|
||||
app.data.readarr_data.prompt_confirm = true;
|
||||
|
||||
app
|
||||
.dispatch_by_readarr_block(&ActiveReadarrBlock::Authors)
|
||||
.await;
|
||||
|
||||
assert!(!app.data.readarr_data.prompt_confirm);
|
||||
assert!(!app.should_refresh);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_check_for_readarr_prompt_action_no_prompt_confirm() {
|
||||
let mut app = App::test_default();
|
||||
app.data.readarr_data.prompt_confirm = false;
|
||||
|
||||
app.check_for_readarr_prompt_action().await;
|
||||
|
||||
assert!(!app.data.readarr_data.prompt_confirm);
|
||||
assert!(!app.should_refresh);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_check_for_readarr_prompt_action_with_no_prompt_confirm_action() {
|
||||
let mut app = App::test_default();
|
||||
app.data.readarr_data.prompt_confirm = true;
|
||||
|
||||
app.check_for_readarr_prompt_action().await;
|
||||
|
||||
assert!(!app.data.readarr_data.prompt_confirm);
|
||||
assert_none!(app.data.readarr_data.prompt_confirm_action);
|
||||
assert!(!app.should_refresh);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_readarr_on_tick_first_render() {
|
||||
let mut app = App::test_default();
|
||||
app.data.readarr_data.prompt_confirm = true;
|
||||
app.is_first_render = true;
|
||||
|
||||
app.readarr_on_tick(ActiveReadarrBlock::Downloads).await;
|
||||
|
||||
assert!(!app.data.readarr_data.prompt_confirm);
|
||||
assert!(!app.is_first_render);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_readarr_on_tick_routing() {
|
||||
let mut app = App::test_default();
|
||||
app.data.readarr_data.prompt_confirm = true;
|
||||
app.is_routing = true;
|
||||
app.should_refresh = true;
|
||||
app.is_first_render = false;
|
||||
app.tick_count = 1;
|
||||
|
||||
app.readarr_on_tick(ActiveReadarrBlock::Downloads).await;
|
||||
|
||||
assert!(!app.data.readarr_data.prompt_confirm);
|
||||
assert!(!app.cancellation_token.is_cancelled());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_readarr_on_tick_routing_while_long_request_is_running_should_cancel_request() {
|
||||
let mut app = App::test_default();
|
||||
app.data.readarr_data.prompt_confirm = true;
|
||||
app.is_routing = true;
|
||||
app.should_refresh = false;
|
||||
app.is_first_render = false;
|
||||
app.tick_count = 1;
|
||||
|
||||
app.readarr_on_tick(ActiveReadarrBlock::Downloads).await;
|
||||
|
||||
assert!(app.cancellation_token.is_cancelled());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_readarr_on_tick_should_refresh() {
|
||||
let mut app = App::test_default();
|
||||
app.data.readarr_data.prompt_confirm = true;
|
||||
app.should_refresh = true;
|
||||
app.is_first_render = false;
|
||||
app.tick_count = 1;
|
||||
|
||||
app.readarr_on_tick(ActiveReadarrBlock::Downloads).await;
|
||||
|
||||
assert!(app.should_refresh);
|
||||
assert!(!app.data.readarr_data.prompt_confirm);
|
||||
assert_eq!(app.tick_count, 0);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use clap_complete::Shell;
|
||||
use indoc::indoc;
|
||||
use lidarr::{LidarrCliHandler, LidarrCommand};
|
||||
use radarr::{RadarrCliHandler, RadarrCommand};
|
||||
use readarr::{ReadarrCliHandler, ReadarrCommand};
|
||||
use sonarr::{SonarrCliHandler, SonarrCommand};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
@@ -13,6 +14,7 @@ use crate::{app::App, network::NetworkTrait};
|
||||
|
||||
pub mod lidarr;
|
||||
pub mod radarr;
|
||||
pub mod readarr;
|
||||
pub mod sonarr;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -30,6 +32,9 @@ pub enum Command {
|
||||
#[command(subcommand, about = "Commands for manging your Lidarr instance")]
|
||||
Lidarr(LidarrCommand),
|
||||
|
||||
#[command(subcommand, about = "Commands for managing your Readarr instance")]
|
||||
Readarr(ReadarrCommand),
|
||||
|
||||
#[command(
|
||||
arg_required_else_help = true,
|
||||
about = "Generate shell completions for the Managarr CLI"
|
||||
@@ -78,6 +83,11 @@ pub(crate) async fn handle_command(
|
||||
.handle()
|
||||
.await?
|
||||
}
|
||||
Command::Readarr(readarr_command) => {
|
||||
ReadarrCliHandler::with(app, readarr_command, network)
|
||||
.handle()
|
||||
.await?
|
||||
}
|
||||
_ => String::new(),
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use super::{CliCommandHandler, Command};
|
||||
use crate::{app::App, network::NetworkTrait};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "readarr_command_tests.rs"]
|
||||
mod readarr_command_tests;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||
pub enum ReadarrCommand {}
|
||||
|
||||
impl From<ReadarrCommand> for Command {
|
||||
fn from(readarr_command: ReadarrCommand) -> Command {
|
||||
Command::Readarr(readarr_command)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct ReadarrCliHandler<'a, 'b> {
|
||||
app: &'a Arc<Mutex<App<'b>>>,
|
||||
command: ReadarrCommand,
|
||||
network: &'a mut dyn NetworkTrait,
|
||||
}
|
||||
|
||||
impl<'a, 'b> CliCommandHandler<'a, 'b, ReadarrCommand> for ReadarrCliHandler<'a, 'b> {
|
||||
fn with(
|
||||
app: &'a Arc<Mutex<App<'b>>>,
|
||||
command: ReadarrCommand,
|
||||
network: &'a mut dyn NetworkTrait,
|
||||
) -> Self {
|
||||
ReadarrCliHandler {
|
||||
app,
|
||||
command,
|
||||
network,
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle(self) -> Result<String> {
|
||||
match self.command {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod cli {
|
||||
use clap::CommandFactory;
|
||||
|
||||
use crate::Cli;
|
||||
|
||||
#[test]
|
||||
fn test_readarr_command_requires_a_subcommand() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "readarr"]);
|
||||
|
||||
assert_err!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ mod tests {
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
|
||||
use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
|
||||
use crate::models::servarr_data::{ActiveKeybindingBlock, Notification};
|
||||
use crate::models::servarr_models::KeybindingItem;
|
||||
@@ -61,9 +62,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(0, ActiveLidarrBlock::Artists, ActiveSonarrBlock::Series)]
|
||||
#[case(0, ActiveReadarrBlock::Authors, ActiveSonarrBlock::Series)]
|
||||
#[case(1, ActiveRadarrBlock::Movies, ActiveLidarrBlock::Artists)]
|
||||
#[case(2, ActiveSonarrBlock::Series, ActiveRadarrBlock::Movies)]
|
||||
#[case(2, ActiveSonarrBlock::Series, ActiveReadarrBlock::Authors)]
|
||||
#[case(3, ActiveLidarrBlock::Artists, ActiveRadarrBlock::Movies)]
|
||||
fn test_handle_change_tabs<T, U>(
|
||||
#[case] index: usize,
|
||||
#[case] left_block: T,
|
||||
@@ -226,6 +228,22 @@ mod tests {
|
||||
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_handle_prompt_toggle_left_right_readarr(#[values(Key::Left, Key::Right)] key: Key) {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveReadarrBlock::Authors.into());
|
||||
|
||||
assert!(!app.data.readarr_data.prompt_confirm);
|
||||
|
||||
handle_prompt_toggle(&mut app, key);
|
||||
|
||||
assert!(app.data.readarr_data.prompt_confirm);
|
||||
|
||||
handle_prompt_toggle(&mut app, key);
|
||||
|
||||
assert!(!app.data.readarr_data.prompt_confirm);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_populate_keymapping_table_global_options() {
|
||||
let expected_keybinding_items = Vec::from(SERVARR_CONTEXT_CLUES)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use lidarr_handlers::LidarrHandler;
|
||||
use radarr_handlers::RadarrHandler;
|
||||
use readarr_handlers::ReadarrHandler;
|
||||
use sonarr_handlers::SonarrHandler;
|
||||
|
||||
use crate::app::App;
|
||||
@@ -18,6 +19,7 @@ use crate::models::{HorizontallyScrollableText, Route};
|
||||
mod keybinding_handler;
|
||||
mod lidarr_handlers;
|
||||
mod radarr_handlers;
|
||||
mod readarr_handlers;
|
||||
mod sonarr_handlers;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -132,6 +134,9 @@ pub fn handle_events(key: Key, app: &mut App<'_>) {
|
||||
Route::Lidarr(active_lidarr_block, context) => {
|
||||
LidarrHandler::new(key, app, active_lidarr_block, context).handle()
|
||||
}
|
||||
Route::Readarr(active_readarr_block, context) => {
|
||||
ReadarrHandler::new(key, app, active_readarr_block, context).handle()
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -197,6 +202,9 @@ fn handle_prompt_toggle(app: &mut App<'_>, key: Key) {
|
||||
Route::Lidarr(_, _) => {
|
||||
app.data.lidarr_data.prompt_confirm = !app.data.lidarr_data.prompt_confirm
|
||||
}
|
||||
Route::Readarr(_, _) => {
|
||||
app.data.readarr_data.prompt_confirm = !app.data.readarr_data.prompt_confirm
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
use super::KeyEventHandler;
|
||||
use crate::models::Route;
|
||||
use crate::{
|
||||
app::App, event::Key, models::servarr_data::readarr::readarr_data::ActiveReadarrBlock,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "readarr_handler_tests.rs"]
|
||||
mod readarr_handler_tests;
|
||||
|
||||
pub(super) struct ReadarrHandler<'a, 'b> {
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
active_readarr_block: ActiveReadarrBlock,
|
||||
context: Option<ActiveReadarrBlock>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveReadarrBlock> for ReadarrHandler<'a, 'b> {
|
||||
fn accepts(_active_block: ActiveReadarrBlock) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
active_block: ActiveReadarrBlock,
|
||||
context: Option<ActiveReadarrBlock>,
|
||||
) -> ReadarrHandler<'a, 'b> {
|
||||
ReadarrHandler {
|
||||
key,
|
||||
app,
|
||||
active_readarr_block: active_block,
|
||||
context,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_key(&self) -> Key {
|
||||
self.key
|
||||
}
|
||||
|
||||
fn ignore_special_keys(&self) -> bool {
|
||||
self.app.ignore_special_keys_for_textbox_input
|
||||
}
|
||||
|
||||
fn is_ready(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn handle_scroll_up(&mut self) {}
|
||||
|
||||
fn handle_scroll_down(&mut self) {}
|
||||
|
||||
fn handle_home(&mut self) {}
|
||||
|
||||
fn handle_end(&mut self) {}
|
||||
|
||||
fn handle_delete(&mut self) {}
|
||||
|
||||
fn handle_left_right_action(&mut self) {}
|
||||
|
||||
fn handle_submit(&mut self) {}
|
||||
|
||||
fn handle_esc(&mut self) {}
|
||||
|
||||
fn handle_char_key_event(&mut self) {}
|
||||
|
||||
fn app_mut(&mut self) -> &mut App<'b> {
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::app::App;
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::readarr_handlers::ReadarrHandler;
|
||||
use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
#[rstest]
|
||||
fn test_readarr_handler_ignore_special_keys(
|
||||
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
|
||||
let handler = ReadarrHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveReadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
handler.ignore_special_keys(),
|
||||
ignore_special_keys_for_textbox_input
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readarr_handler_is_ready() {
|
||||
let mut app = App::test_default();
|
||||
app.is_loading = true;
|
||||
|
||||
let handler = ReadarrHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveReadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(handler.is_ready());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readarr_handler_accepts() {
|
||||
for readarr_block in ActiveReadarrBlock::iter() {
|
||||
assert!(ReadarrHandler::accepts(readarr_block));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,6 @@ pub const BAZARR_LOGO: &str = "⠀⠀⠀⠀⠀⠀⠀⣀⠠⠄⠠⠄⣀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠑⢌⠉⠉⠉⠉⠉⠉⡠⠊⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠉⠒⠂⠐⠒⠉⠀⠀⠀⠀⠀⠀⠀
|
||||
";
|
||||
// Allowing this code for now since we'll eventually be implementing additional Servarr support and we'll need it then
|
||||
#[allow(dead_code)]
|
||||
pub const READARR_LOGO: &str = "⠀⠀⠀⠀⠀⣀⣠⣤⣄⣀⠀⠀⠀⠀⠀
|
||||
⠀⠀⢀⡴⠛⠉⠀⠀⠀⠉⠛⢦⡀⠀⠀
|
||||
⠀⢠⣯⣄⣀⣐⠻⣿⠟⣂⣀⣠⣽⡄⠀
|
||||
|
||||
+1
-1
@@ -173,7 +173,7 @@ async fn main() -> Result<()> {
|
||||
|
||||
match args.command {
|
||||
Some(command) => match command {
|
||||
Command::Radarr(_) | Command::Sonarr(_) | Command::Lidarr(_) => {
|
||||
Command::Radarr(_) | Command::Sonarr(_) | Command::Lidarr(_) | Command::Readarr(_) => {
|
||||
if spinner_disabled {
|
||||
start_cli_no_spinner(config, reqwest_client, cancellation_token, app, command).await;
|
||||
} else {
|
||||
|
||||
+5
-1
@@ -5,8 +5,10 @@ use crate::app::ServarrConfig;
|
||||
use crate::app::context_clues::ContextClue;
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock;
|
||||
use lidarr_models::LidarrSerdeable;
|
||||
use radarr_models::RadarrSerdeable;
|
||||
use readarr_models::ReadarrSerdeable;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
|
||||
use serde_json::Number;
|
||||
@@ -15,6 +17,7 @@ use sonarr_models::SonarrSerdeable;
|
||||
|
||||
pub mod lidarr_models;
|
||||
pub mod radarr_models;
|
||||
pub mod readarr_models;
|
||||
pub mod servarr_data;
|
||||
pub mod servarr_models;
|
||||
pub mod sonarr_models;
|
||||
@@ -32,7 +35,7 @@ mod model_tests;
|
||||
pub enum Route {
|
||||
Radarr(ActiveRadarrBlock, Option<ActiveRadarrBlock>),
|
||||
Sonarr(ActiveSonarrBlock, Option<ActiveSonarrBlock>),
|
||||
Readarr,
|
||||
Readarr(ActiveReadarrBlock, Option<ActiveReadarrBlock>),
|
||||
Lidarr(ActiveLidarrBlock, Option<ActiveLidarrBlock>),
|
||||
Whisparr,
|
||||
Bazarr,
|
||||
@@ -47,6 +50,7 @@ pub enum Serdeable {
|
||||
Radarr(RadarrSerdeable),
|
||||
Sonarr(SonarrSerdeable),
|
||||
Lidarr(LidarrSerdeable),
|
||||
Readarr(ReadarrSerdeable),
|
||||
}
|
||||
|
||||
pub trait Scrollable {
|
||||
|
||||
@@ -0,0 +1,501 @@
|
||||
use super::{
|
||||
HorizontallyScrollableText, Serdeable,
|
||||
servarr_models::{
|
||||
DiskSpace, HostConfig, Indexer, IndexerTestResult, MetadataProfile, QualityProfile,
|
||||
QualityWrapper, RootFolder, SecurityConfig, SystemStatus, Tag,
|
||||
},
|
||||
};
|
||||
use crate::models::servarr_models::{IndexerSettings, LogResponse, QueueEvent, Update};
|
||||
use crate::serde_enum_from;
|
||||
use chrono::{DateTime, Utc};
|
||||
use clap::ValueEnum;
|
||||
use derivative::Derivative;
|
||||
use enum_display_style_derive::EnumDisplayStyle;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Number, Value};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use strum::{Display, EnumIter};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "readarr_models_tests.rs"]
|
||||
mod readarr_models_tests;
|
||||
|
||||
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Author {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub id: i64,
|
||||
pub author_name: HorizontallyScrollableText,
|
||||
pub foreign_author_id: String,
|
||||
pub status: AuthorStatus,
|
||||
pub overview: Option<String>,
|
||||
pub author_type: Option<String>,
|
||||
pub disambiguation: Option<String>,
|
||||
pub path: String,
|
||||
pub root_folder_path: Option<String>,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub quality_profile_id: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub metadata_profile_id: i64,
|
||||
pub monitored: bool,
|
||||
pub monitor_new_items: NewItemMonitorType,
|
||||
pub genres: Vec<String>,
|
||||
pub tags: Vec<Number>,
|
||||
pub ratings: Option<Ratings>,
|
||||
pub statistics: Option<AuthorStatistics>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, Display, EnumDisplayStyle,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum AuthorStatus {
|
||||
#[default]
|
||||
Continuing,
|
||||
Ended,
|
||||
Deleted,
|
||||
}
|
||||
|
||||
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Ratings {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub votes: i64,
|
||||
#[serde(deserialize_with = "super::from_f64")]
|
||||
pub value: f64,
|
||||
pub popularity: Option<f64>,
|
||||
}
|
||||
|
||||
impl Eq for Ratings {}
|
||||
|
||||
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthorStatistics {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub book_count: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub book_file_count: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub total_book_count: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub available_book_count: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub size_on_disk: i64,
|
||||
#[serde(deserialize_with = "super::from_f64")]
|
||||
pub percent_of_books: f64,
|
||||
}
|
||||
|
||||
impl Eq for AuthorStatistics {}
|
||||
|
||||
#[derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
EnumIter,
|
||||
clap::ValueEnum,
|
||||
Display,
|
||||
EnumDisplayStyle,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum NewItemMonitorType {
|
||||
#[default]
|
||||
#[display_style(name = "All Books")]
|
||||
All,
|
||||
#[display_style(name = "No New Books")]
|
||||
None,
|
||||
#[display_style(name = "New Books")]
|
||||
New,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
EnumIter,
|
||||
clap::ValueEnum,
|
||||
Display,
|
||||
EnumDisplayStyle,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum MonitorType {
|
||||
#[default]
|
||||
#[display_style(name = "All Books")]
|
||||
All,
|
||||
#[display_style(name = "Future Books")]
|
||||
Future,
|
||||
#[display_style(name = "Missing Books")]
|
||||
Missing,
|
||||
#[display_style(name = "Existing Books")]
|
||||
Existing,
|
||||
#[display_style(name = "First Book")]
|
||||
First,
|
||||
#[display_style(name = "Latest Book")]
|
||||
Latest,
|
||||
None,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DownloadRecord {
|
||||
pub title: String,
|
||||
pub status: DownloadStatus,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub id: i64,
|
||||
pub book_id: Option<Number>,
|
||||
pub author_id: Option<Number>,
|
||||
#[serde(deserialize_with = "super::from_f64")]
|
||||
pub size: f64,
|
||||
#[serde(deserialize_with = "super::from_f64")]
|
||||
pub sizeleft: f64,
|
||||
pub output_path: Option<HorizontallyScrollableText>,
|
||||
#[serde(default)]
|
||||
pub indexer: String,
|
||||
pub download_client: Option<String>,
|
||||
}
|
||||
|
||||
impl Eq for DownloadRecord {}
|
||||
|
||||
#[derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
EnumIter,
|
||||
Display,
|
||||
EnumDisplayStyle,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum DownloadStatus {
|
||||
#[default]
|
||||
Unknown,
|
||||
Queued,
|
||||
Paused,
|
||||
Downloading,
|
||||
Completed,
|
||||
Failed,
|
||||
Warning,
|
||||
Delay,
|
||||
#[display_style(name = "Download Client Unavailable")]
|
||||
DownloadClientUnavailable,
|
||||
Fallback,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DownloadsResponse {
|
||||
pub records: Vec<DownloadRecord>,
|
||||
}
|
||||
|
||||
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AddAuthorSearchResult {
|
||||
pub foreign_author_id: String,
|
||||
pub author_name: HorizontallyScrollableText,
|
||||
pub status: AuthorStatus,
|
||||
#[serde(default)]
|
||||
pub ended: bool,
|
||||
pub overview: Option<String>,
|
||||
pub author_type: Option<String>,
|
||||
pub disambiguation: Option<String>,
|
||||
pub genres: Vec<String>,
|
||||
pub ratings: Option<Ratings>,
|
||||
}
|
||||
|
||||
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Book {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub id: i64,
|
||||
pub title: HorizontallyScrollableText,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub author_id: i64,
|
||||
pub foreign_book_id: String,
|
||||
pub monitored: bool,
|
||||
#[serde(default)]
|
||||
pub any_edition_ok: bool,
|
||||
pub page_count: Option<i64>,
|
||||
pub ratings: Option<Ratings>,
|
||||
pub release_date: Option<DateTime<Utc>>,
|
||||
pub statistics: Option<BookStatistics>,
|
||||
pub grabbed: bool,
|
||||
}
|
||||
|
||||
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BookStatistics {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub book_file_count: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub total_book_count: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub size_on_disk: i64,
|
||||
#[serde(deserialize_with = "super::from_f64")]
|
||||
pub percent_of_books: f64,
|
||||
}
|
||||
|
||||
impl Eq for BookStatistics {}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Edition {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub id: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub book_id: i64,
|
||||
pub foreign_edition_id: String,
|
||||
pub monitored: bool,
|
||||
pub is_ebook: bool,
|
||||
pub title: String,
|
||||
pub language: Option<String>,
|
||||
pub overview: Option<String>,
|
||||
pub format: Option<String>,
|
||||
pub publisher: Option<String>,
|
||||
pub page_count: Option<i64>,
|
||||
pub release_date: Option<DateTime<Utc>>,
|
||||
pub isbn13: Option<String>,
|
||||
pub asin: Option<String>,
|
||||
pub ratings: Option<Ratings>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReadarrHistoryWrapper {
|
||||
pub records: Vec<ReadarrHistoryItem>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReadarrHistoryData {
|
||||
pub indexer: Option<String>,
|
||||
pub release_group: Option<String>,
|
||||
pub nzb_info_url: Option<String>,
|
||||
pub download_client_name: Option<String>,
|
||||
pub download_client: Option<String>,
|
||||
pub age: Option<String>,
|
||||
pub published_date: Option<DateTime<Utc>>,
|
||||
pub message: Option<String>,
|
||||
pub reason: Option<String>,
|
||||
pub dropped_path: Option<String>,
|
||||
pub imported_path: Option<String>,
|
||||
pub source_path: Option<String>,
|
||||
pub path: Option<String>,
|
||||
pub status_messages: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Display, EnumDisplayStyle,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum ReadarrHistoryEventType {
|
||||
#[default]
|
||||
Unknown,
|
||||
Grabbed,
|
||||
#[display_style(name = "Author Folder Imported")]
|
||||
AuthorFolderImported,
|
||||
#[display_style(name = "Book Import Incomplete")]
|
||||
BookImportIncomplete,
|
||||
#[display_style(name = "Download Ignored")]
|
||||
DownloadIgnored,
|
||||
#[display_style(name = "Download Imported")]
|
||||
DownloadImported,
|
||||
#[display_style(name = "Download Failed")]
|
||||
DownloadFailed,
|
||||
#[display_style(name = "Book File Deleted")]
|
||||
BookFileDeleted,
|
||||
#[display_style(name = "Book File Imported")]
|
||||
BookFileImported,
|
||||
#[display_style(name = "Book File Renamed")]
|
||||
BookFileRenamed,
|
||||
#[display_style(name = "Book File Retagged")]
|
||||
BookFileRetagged,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReadarrHistoryItem {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub id: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub author_id: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub book_id: i64,
|
||||
pub source_title: HorizontallyScrollableText,
|
||||
#[serde(default)]
|
||||
pub quality: QualityWrapper,
|
||||
pub date: DateTime<Utc>,
|
||||
pub event_type: ReadarrHistoryEventType,
|
||||
#[serde(default)]
|
||||
pub data: ReadarrHistoryData,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReadarrTask {
|
||||
pub name: String,
|
||||
pub task_name: ReadarrTaskName,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub interval: i64,
|
||||
pub last_execution: DateTime<Utc>,
|
||||
pub last_duration: String,
|
||||
pub next_execution: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Copy, ValueEnum)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub enum ReadarrTaskName {
|
||||
#[default]
|
||||
ApplicationUpdateCheck,
|
||||
Backup,
|
||||
CheckHealth,
|
||||
Housekeeping,
|
||||
ImportListSync,
|
||||
MessagingCleanup,
|
||||
RefreshAuthor,
|
||||
RefreshMonitoredDownloads,
|
||||
RescanFolders,
|
||||
RssSync,
|
||||
}
|
||||
|
||||
impl Display for ReadarrTaskName {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let task_name = serde_json::to_string(&self)
|
||||
.expect("Unable to serialize task name")
|
||||
.replace('"', "");
|
||||
write!(f, "{task_name}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(default)]
|
||||
pub struct ReadarrRelease {
|
||||
pub guid: String,
|
||||
pub protocol: String,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub age: i64,
|
||||
pub title: HorizontallyScrollableText,
|
||||
pub author_name: Option<String>,
|
||||
pub book_title: Option<String>,
|
||||
pub indexer: String,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub indexer_id: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub size: i64,
|
||||
pub rejected: bool,
|
||||
pub rejections: Option<Vec<String>>,
|
||||
pub seeders: Option<Number>,
|
||||
pub leechers: Option<Number>,
|
||||
pub quality: QualityWrapper,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BlocklistItem {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub id: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub author_id: i64,
|
||||
pub book_ids: Option<Vec<Number>>,
|
||||
pub source_title: String,
|
||||
pub quality: QualityWrapper,
|
||||
pub date: DateTime<Utc>,
|
||||
pub protocol: String,
|
||||
pub indexer: String,
|
||||
pub message: String,
|
||||
pub author: Author,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BlocklistResponse {
|
||||
pub records: Vec<BlocklistItem>,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BookFile {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub id: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub author_id: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub book_id: i64,
|
||||
pub path: String,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub size: i64,
|
||||
pub date_added: DateTime<Utc>,
|
||||
pub quality: QualityWrapper,
|
||||
pub media_info: Option<MediaInfo>,
|
||||
pub quality_cutoff_not_met: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Derivative, Debug, Clone, PartialEq, Eq)]
|
||||
#[derivative(Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MediaInfo {
|
||||
pub audio_bit_rate: Option<String>,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub audio_channels: i64,
|
||||
pub audio_codec: Option<String>,
|
||||
pub audio_bits: Option<String>,
|
||||
pub audio_sample_rate: Option<String>,
|
||||
}
|
||||
|
||||
impl From<ReadarrSerdeable> for Serdeable {
|
||||
fn from(value: ReadarrSerdeable) -> Serdeable {
|
||||
Serdeable::Readarr(value)
|
||||
}
|
||||
}
|
||||
|
||||
serde_enum_from!(
|
||||
ReadarrSerdeable {
|
||||
AddAuthorSearchResults(Vec<AddAuthorSearchResult>),
|
||||
Author(Author),
|
||||
Authors(Vec<Author>),
|
||||
BlocklistResponse(BlocklistResponse),
|
||||
Book(Book),
|
||||
BookFiles(Vec<BookFile>),
|
||||
Books(Vec<Book>),
|
||||
DiskSpaces(Vec<DiskSpace>),
|
||||
DownloadsResponse(DownloadsResponse),
|
||||
Editions(Vec<Edition>),
|
||||
HostConfig(HostConfig),
|
||||
Indexers(Vec<Indexer>),
|
||||
IndexerSettings(IndexerSettings),
|
||||
IndexerTestResults(Vec<IndexerTestResult>),
|
||||
LogResponse(LogResponse),
|
||||
MetadataProfiles(Vec<MetadataProfile>),
|
||||
QualityProfiles(Vec<QualityProfile>),
|
||||
QueueEvents(Vec<QueueEvent>),
|
||||
ReadarrHistoryItems(Vec<ReadarrHistoryItem>),
|
||||
ReadarrHistoryWrapper(ReadarrHistoryWrapper),
|
||||
Releases(Vec<ReadarrRelease>),
|
||||
RootFolders(Vec<RootFolder>),
|
||||
SecurityConfig(SecurityConfig),
|
||||
SystemStatus(SystemStatus),
|
||||
Tag(Tag),
|
||||
Tags(Vec<Tag>),
|
||||
Tasks(Vec<ReadarrTask>),
|
||||
Updates(Vec<Update>),
|
||||
Value(Value),
|
||||
}
|
||||
);
|
||||
File diff suppressed because it is too large.
Load diff
@@ -3,6 +3,7 @@ use crate::models::Route;
|
||||
pub mod lidarr;
|
||||
pub mod modals;
|
||||
pub mod radarr;
|
||||
pub mod readarr;
|
||||
pub mod sonarr;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod modals;
|
||||
pub mod readarr_data;
|
||||
@@ -0,0 +1,271 @@
|
||||
use super::readarr_data::{ActiveReadarrBlock, ReadarrData};
|
||||
use crate::app::readarr::readarr_context_clues::{
|
||||
BOOK_DETAILS_CONTEXT_CLUES, BOOK_HISTORY_CONTEXT_CLUES, MANUAL_BOOK_SEARCH_CONTEXT_CLUES,
|
||||
};
|
||||
use crate::models::readarr_models::{BookFile, Edition, ReadarrHistoryItem, ReadarrRelease};
|
||||
use crate::models::servarr_data::modals::EditIndexerModal;
|
||||
use crate::models::servarr_models::Indexer;
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use crate::models::{
|
||||
HorizontallyScrollableText, ScrollableText, TabRoute, TabState,
|
||||
readarr_models::{MonitorType, NewItemMonitorType},
|
||||
servarr_models::RootFolder,
|
||||
stateful_list::StatefulList,
|
||||
};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "modals_tests.rs"]
|
||||
mod modals_tests;
|
||||
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
pub struct AddAuthorModal {
|
||||
pub root_folder_list: StatefulList<RootFolder>,
|
||||
pub monitor_list: StatefulList<MonitorType>,
|
||||
pub monitor_new_items_list: StatefulList<NewItemMonitorType>,
|
||||
pub quality_profile_list: StatefulList<String>,
|
||||
pub metadata_profile_list: StatefulList<String>,
|
||||
pub tags: HorizontallyScrollableText,
|
||||
}
|
||||
|
||||
impl From<&ReadarrData<'_>> for AddAuthorModal {
|
||||
fn from(readarr_data: &ReadarrData<'_>) -> AddAuthorModal {
|
||||
let mut add_author_modal = AddAuthorModal::default();
|
||||
add_author_modal
|
||||
.monitor_list
|
||||
.set_items(Vec::from_iter(MonitorType::iter()));
|
||||
add_author_modal
|
||||
.monitor_new_items_list
|
||||
.set_items(Vec::from_iter(NewItemMonitorType::iter()));
|
||||
add_author_modal
|
||||
.quality_profile_list
|
||||
.set_items(readarr_data.sorted_quality_profile_names());
|
||||
add_author_modal
|
||||
.metadata_profile_list
|
||||
.set_items(readarr_data.sorted_metadata_profile_names());
|
||||
add_author_modal
|
||||
.root_folder_list
|
||||
.set_items(readarr_data.root_folders.items.to_vec());
|
||||
|
||||
add_author_modal
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
pub struct EditAuthorModal {
|
||||
pub monitor_list: StatefulList<NewItemMonitorType>,
|
||||
pub quality_profile_list: StatefulList<String>,
|
||||
pub metadata_profile_list: StatefulList<String>,
|
||||
pub monitored: Option<bool>,
|
||||
pub path: HorizontallyScrollableText,
|
||||
pub tags: HorizontallyScrollableText,
|
||||
}
|
||||
|
||||
impl From<&ReadarrData<'_>> for EditAuthorModal {
|
||||
fn from(readarr_data: &ReadarrData<'_>) -> EditAuthorModal {
|
||||
let mut edit_author_modal = EditAuthorModal::default();
|
||||
let author = readarr_data.authors.current_selection();
|
||||
|
||||
edit_author_modal
|
||||
.monitor_list
|
||||
.set_items(Vec::from_iter(NewItemMonitorType::iter()));
|
||||
edit_author_modal.path = author.path.clone().into();
|
||||
edit_author_modal.tags = readarr_data.tag_ids_to_display(&author.tags).into();
|
||||
edit_author_modal.monitored = Some(author.monitored);
|
||||
|
||||
let monitor_index = edit_author_modal
|
||||
.monitor_list
|
||||
.items
|
||||
.iter()
|
||||
.position(|m| *m == author.monitor_new_items);
|
||||
edit_author_modal.monitor_list.state.select(monitor_index);
|
||||
|
||||
edit_author_modal
|
||||
.quality_profile_list
|
||||
.set_items(readarr_data.sorted_quality_profile_names());
|
||||
let quality_profile_name = readarr_data
|
||||
.quality_profile_map
|
||||
.get_by_left(&author.quality_profile_id)
|
||||
.unwrap();
|
||||
let quality_profile_index = edit_author_modal
|
||||
.quality_profile_list
|
||||
.items
|
||||
.iter()
|
||||
.position(|profile| profile == quality_profile_name);
|
||||
edit_author_modal
|
||||
.quality_profile_list
|
||||
.state
|
||||
.select(quality_profile_index);
|
||||
|
||||
edit_author_modal
|
||||
.metadata_profile_list
|
||||
.set_items(readarr_data.sorted_metadata_profile_names());
|
||||
let metadata_profile_name = readarr_data
|
||||
.metadata_profile_map
|
||||
.get_by_left(&author.metadata_profile_id)
|
||||
.unwrap();
|
||||
let metadata_profile_index = edit_author_modal
|
||||
.metadata_profile_list
|
||||
.items
|
||||
.iter()
|
||||
.position(|profile| profile == metadata_profile_name);
|
||||
edit_author_modal
|
||||
.metadata_profile_list
|
||||
.state
|
||||
.select(metadata_profile_index);
|
||||
|
||||
edit_author_modal
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ReadarrData<'_>> for EditIndexerModal {
|
||||
fn from(readarr_data: &ReadarrData<'_>) -> EditIndexerModal {
|
||||
let mut edit_indexer_modal = EditIndexerModal::default();
|
||||
let Indexer {
|
||||
name,
|
||||
enable_rss,
|
||||
enable_automatic_search,
|
||||
enable_interactive_search,
|
||||
tags,
|
||||
fields,
|
||||
priority,
|
||||
..
|
||||
} = readarr_data.indexers.current_selection();
|
||||
let seed_ratio_field_option = fields
|
||||
.as_ref()
|
||||
.expect("indexer fields must exist")
|
||||
.iter()
|
||||
.find(|field| {
|
||||
field.name.as_ref().expect("indexer field name must exist") == "seedCriteria.seedRatio"
|
||||
});
|
||||
let seed_ratio_value_option = if let Some(seed_ratio_field) = seed_ratio_field_option {
|
||||
seed_ratio_field.value.clone()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
edit_indexer_modal.name = name.clone().expect("indexer name must exist").into();
|
||||
edit_indexer_modal.enable_rss = Some(*enable_rss);
|
||||
edit_indexer_modal.enable_automatic_search = Some(*enable_automatic_search);
|
||||
edit_indexer_modal.enable_interactive_search = Some(*enable_interactive_search);
|
||||
edit_indexer_modal.priority = *priority;
|
||||
edit_indexer_modal.url = fields
|
||||
.as_ref()
|
||||
.expect("indexer fields must exist")
|
||||
.iter()
|
||||
.find(|field| field.name.as_ref().expect("indexer field name must exist") == "baseUrl")
|
||||
.expect("baseUrl field must exist")
|
||||
.value
|
||||
.clone()
|
||||
.expect("baseUrl field value must exist")
|
||||
.as_str()
|
||||
.expect("baseUrl field value must be a string")
|
||||
.into();
|
||||
edit_indexer_modal.api_key = fields
|
||||
.as_ref()
|
||||
.expect("indexer fields must exist")
|
||||
.iter()
|
||||
.find(|field| field.name.as_ref().expect("indexer field name must exist") == "apiKey")
|
||||
.expect("apiKey field must exist")
|
||||
.value
|
||||
.clone()
|
||||
.expect("apiKey field value must exist")
|
||||
.as_str()
|
||||
.expect("apiKey field value must be a string")
|
||||
.into();
|
||||
|
||||
if let Some(seed_ratio_value) = seed_ratio_value_option {
|
||||
edit_indexer_modal.seed_ratio = seed_ratio_value
|
||||
.as_f64()
|
||||
.expect("Seed ratio value must be a valid f64")
|
||||
.to_string()
|
||||
.into();
|
||||
}
|
||||
|
||||
edit_indexer_modal.tags = readarr_data.tag_ids_to_display(tags).into();
|
||||
|
||||
edit_indexer_modal
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
pub struct AddReadarrRootFolderModal {
|
||||
pub name: HorizontallyScrollableText,
|
||||
pub path: HorizontallyScrollableText,
|
||||
pub monitor_list: StatefulList<MonitorType>,
|
||||
pub monitor_new_items_list: StatefulList<NewItemMonitorType>,
|
||||
pub quality_profile_list: StatefulList<String>,
|
||||
pub metadata_profile_list: StatefulList<String>,
|
||||
pub tags: HorizontallyScrollableText,
|
||||
}
|
||||
|
||||
impl From<&ReadarrData<'_>> for AddReadarrRootFolderModal {
|
||||
fn from(readarr_data: &ReadarrData<'_>) -> AddReadarrRootFolderModal {
|
||||
let mut add_root_folder_modal = AddReadarrRootFolderModal::default();
|
||||
add_root_folder_modal
|
||||
.monitor_list
|
||||
.set_items(Vec::from_iter(MonitorType::iter()));
|
||||
add_root_folder_modal
|
||||
.monitor_new_items_list
|
||||
.set_items(Vec::from_iter(NewItemMonitorType::iter()));
|
||||
add_root_folder_modal
|
||||
.quality_profile_list
|
||||
.set_items(readarr_data.sorted_quality_profile_names());
|
||||
add_root_folder_modal
|
||||
.metadata_profile_list
|
||||
.set_items(readarr_data.sorted_metadata_profile_names());
|
||||
|
||||
add_root_folder_modal
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
pub struct BookDetailsModal {
|
||||
pub editions: StatefulTable<Edition>,
|
||||
pub book_files: StatefulTable<BookFile>,
|
||||
pub edition_details_modal: Option<EditionDetailsModal>,
|
||||
pub book_history: StatefulTable<ReadarrHistoryItem>,
|
||||
pub book_releases: StatefulTable<ReadarrRelease>,
|
||||
pub book_details_tabs: TabState,
|
||||
}
|
||||
|
||||
impl Default for BookDetailsModal {
|
||||
fn default() -> BookDetailsModal {
|
||||
BookDetailsModal {
|
||||
editions: StatefulTable::default(),
|
||||
edition_details_modal: None,
|
||||
book_files: StatefulTable::default(),
|
||||
book_releases: StatefulTable::default(),
|
||||
book_history: StatefulTable::default(),
|
||||
book_details_tabs: TabState::new(vec![
|
||||
TabRoute {
|
||||
title: "Editions".to_string(),
|
||||
route: ActiveReadarrBlock::BookDetails.into(),
|
||||
contextual_help: Some(&BOOK_DETAILS_CONTEXT_CLUES),
|
||||
config: None,
|
||||
},
|
||||
TabRoute {
|
||||
title: "History".to_string(),
|
||||
route: ActiveReadarrBlock::BookHistory.into(),
|
||||
contextual_help: Some(&BOOK_HISTORY_CONTEXT_CLUES),
|
||||
config: None,
|
||||
},
|
||||
TabRoute {
|
||||
title: "Manual Search".to_string(),
|
||||
route: ActiveReadarrBlock::ManualBookSearch.into(),
|
||||
contextual_help: Some(&MANUAL_BOOK_SEARCH_CONTEXT_CLUES),
|
||||
config: None,
|
||||
},
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
pub struct EditionDetailsModal {
|
||||
pub edition_details: ScrollableText,
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::app::readarr::readarr_context_clues::{
|
||||
BOOK_DETAILS_CONTEXT_CLUES, BOOK_HISTORY_CONTEXT_CLUES, MANUAL_BOOK_SEARCH_CONTEXT_CLUES,
|
||||
};
|
||||
use crate::models::readarr_models::{Author, MonitorType, NewItemMonitorType};
|
||||
use crate::models::servarr_data::modals::EditIndexerModal;
|
||||
use crate::models::servarr_data::readarr::modals::{
|
||||
AddAuthorModal, AddReadarrRootFolderModal, BookDetailsModal, EditAuthorModal,
|
||||
EditionDetailsModal,
|
||||
};
|
||||
use crate::models::servarr_data::readarr::readarr_data::{ActiveReadarrBlock, ReadarrData};
|
||||
use crate::models::servarr_models::{Indexer, IndexerField, RootFolder};
|
||||
use bimap::BiMap;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use rstest::rstest;
|
||||
use serde_json::{Number, Value};
|
||||
|
||||
#[test]
|
||||
fn test_add_author_modal_from_readarr_data() {
|
||||
let mut readarr_data = ReadarrData {
|
||||
quality_profile_map: BiMap::from_iter([
|
||||
(2i64, "Lossless".to_owned()),
|
||||
(1i64, "Standard".to_owned()),
|
||||
]),
|
||||
metadata_profile_map: BiMap::from_iter([
|
||||
(2i64, "None".to_owned()),
|
||||
(1i64, "Standard".to_owned()),
|
||||
]),
|
||||
..ReadarrData::default()
|
||||
};
|
||||
let root_folder_1 = RootFolder {
|
||||
id: 1,
|
||||
path: "/nfs".to_owned(),
|
||||
accessible: true,
|
||||
free_space: 219902325555200,
|
||||
unmapped_folders: None,
|
||||
};
|
||||
readarr_data.root_folders.set_items(vec![
|
||||
root_folder_1.clone(),
|
||||
RootFolder {
|
||||
id: 2,
|
||||
path: "/nfs2".to_owned(),
|
||||
accessible: true,
|
||||
free_space: 21990232555520,
|
||||
unmapped_folders: None,
|
||||
},
|
||||
]);
|
||||
|
||||
let add_author_modal = AddAuthorModal::from(&readarr_data);
|
||||
|
||||
assert_eq!(
|
||||
*add_author_modal.monitor_list.current_selection(),
|
||||
MonitorType::default()
|
||||
);
|
||||
assert_eq!(
|
||||
*add_author_modal.monitor_new_items_list.current_selection(),
|
||||
NewItemMonitorType::default()
|
||||
);
|
||||
assert_str_eq!(
|
||||
add_author_modal.quality_profile_list.current_selection(),
|
||||
"Standard"
|
||||
);
|
||||
assert_str_eq!(
|
||||
add_author_modal.metadata_profile_list.current_selection(),
|
||||
"Standard"
|
||||
);
|
||||
assert_eq!(
|
||||
add_author_modal.root_folder_list.current_selection(),
|
||||
&root_folder_1
|
||||
);
|
||||
assert_is_empty!(add_author_modal.tags.text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_author_modal_from_readarr_data() {
|
||||
let mut readarr_data = ReadarrData {
|
||||
quality_profile_map: BiMap::from_iter([
|
||||
(1i64, "HD - 1080p".to_owned()),
|
||||
(2i64, "Any".to_owned()),
|
||||
]),
|
||||
metadata_profile_map: BiMap::from_iter([
|
||||
(1i64, "Standard".to_owned()),
|
||||
(2i64, "None".to_owned()),
|
||||
]),
|
||||
tags_map: BiMap::from_iter([(1i64, "usenet".to_owned())]),
|
||||
..ReadarrData::default()
|
||||
};
|
||||
let author = Author {
|
||||
id: 1,
|
||||
monitored: true,
|
||||
monitor_new_items: NewItemMonitorType::New,
|
||||
quality_profile_id: 1,
|
||||
metadata_profile_id: 1,
|
||||
path: "/nfs/books/test_author".to_owned(),
|
||||
tags: vec![Number::from(1)],
|
||||
..Author::default()
|
||||
};
|
||||
readarr_data.authors.set_items(vec![author]);
|
||||
|
||||
let edit_author_modal = EditAuthorModal::from(&readarr_data);
|
||||
|
||||
assert_some_eq_x!(&edit_author_modal.monitored, &true);
|
||||
assert_eq!(
|
||||
*edit_author_modal.monitor_list.current_selection(),
|
||||
NewItemMonitorType::New
|
||||
);
|
||||
assert_str_eq!(
|
||||
edit_author_modal.quality_profile_list.current_selection(),
|
||||
"HD - 1080p"
|
||||
);
|
||||
assert_str_eq!(
|
||||
edit_author_modal.metadata_profile_list.current_selection(),
|
||||
"Standard"
|
||||
);
|
||||
assert_str_eq!(edit_author_modal.path.text, "/nfs/books/test_author");
|
||||
assert_str_eq!(edit_author_modal.tags.text, "usenet");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_edit_indexer_modal_from_readarr_data(#[values(true, false)] seed_ratio_present: bool) {
|
||||
let mut readarr_data = ReadarrData {
|
||||
tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]),
|
||||
..ReadarrData::default()
|
||||
};
|
||||
let mut fields = vec![
|
||||
IndexerField {
|
||||
name: Some("baseUrl".to_owned()),
|
||||
value: Some(Value::String("https://test.com".to_owned())),
|
||||
},
|
||||
IndexerField {
|
||||
name: Some("apiKey".to_owned()),
|
||||
value: Some(Value::String("1234".to_owned())),
|
||||
},
|
||||
];
|
||||
|
||||
if seed_ratio_present {
|
||||
fields.push(IndexerField {
|
||||
name: Some("seedCriteria.seedRatio".to_owned()),
|
||||
value: Some(Value::from(1.2f64)),
|
||||
});
|
||||
}
|
||||
|
||||
let indexer = Indexer {
|
||||
name: Some("Test".to_owned()),
|
||||
enable_rss: true,
|
||||
enable_automatic_search: true,
|
||||
enable_interactive_search: true,
|
||||
tags: vec![Number::from(1), Number::from(2)],
|
||||
fields: Some(fields),
|
||||
priority: 1,
|
||||
..Indexer::default()
|
||||
};
|
||||
readarr_data.indexers.set_items(vec![indexer]);
|
||||
|
||||
let edit_indexer_modal = EditIndexerModal::from(&readarr_data);
|
||||
|
||||
assert_str_eq!(edit_indexer_modal.name.text, "Test");
|
||||
assert_some_eq_x!(&edit_indexer_modal.enable_rss, &true);
|
||||
assert_some_eq_x!(&edit_indexer_modal.enable_automatic_search, &true);
|
||||
assert_some_eq_x!(&edit_indexer_modal.enable_interactive_search, &true);
|
||||
assert_eq!(edit_indexer_modal.priority, 1);
|
||||
assert_str_eq!(edit_indexer_modal.url.text, "https://test.com");
|
||||
assert_str_eq!(edit_indexer_modal.api_key.text, "1234");
|
||||
|
||||
if seed_ratio_present {
|
||||
assert_str_eq!(edit_indexer_modal.seed_ratio.text, "1.2");
|
||||
} else {
|
||||
assert_is_empty!(edit_indexer_modal.seed_ratio.text);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_modal_from_readarr_data_seed_ratio_value_is_none() {
|
||||
let mut readarr_data = ReadarrData {
|
||||
tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]),
|
||||
..ReadarrData::default()
|
||||
};
|
||||
let fields = vec![
|
||||
IndexerField {
|
||||
name: Some("baseUrl".to_owned()),
|
||||
value: Some(Value::String("https://test.com".to_owned())),
|
||||
},
|
||||
IndexerField {
|
||||
name: Some("apiKey".to_owned()),
|
||||
value: Some(Value::String("1234".to_owned())),
|
||||
},
|
||||
IndexerField {
|
||||
name: Some("seedCriteria.seedRatio".to_owned()),
|
||||
value: None,
|
||||
},
|
||||
];
|
||||
|
||||
let indexer = Indexer {
|
||||
name: Some("Test".to_owned()),
|
||||
enable_rss: true,
|
||||
enable_automatic_search: true,
|
||||
enable_interactive_search: true,
|
||||
tags: vec![Number::from(1), Number::from(2)],
|
||||
fields: Some(fields),
|
||||
priority: 1,
|
||||
..Indexer::default()
|
||||
};
|
||||
readarr_data.indexers.set_items(vec![indexer]);
|
||||
|
||||
let edit_indexer_modal = EditIndexerModal::from(&readarr_data);
|
||||
|
||||
assert_str_eq!(edit_indexer_modal.name.text, "Test");
|
||||
assert_some_eq_x!(&edit_indexer_modal.enable_rss, &true);
|
||||
assert_some_eq_x!(&edit_indexer_modal.enable_automatic_search, &true);
|
||||
assert_some_eq_x!(&edit_indexer_modal.enable_interactive_search, &true);
|
||||
assert_eq!(edit_indexer_modal.priority, 1);
|
||||
assert_str_eq!(edit_indexer_modal.url.text, "https://test.com");
|
||||
assert_str_eq!(edit_indexer_modal.api_key.text, "1234");
|
||||
assert_is_empty!(edit_indexer_modal.seed_ratio.text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_readarr_root_folder_modal_from_readarr_data() {
|
||||
let readarr_data = ReadarrData {
|
||||
quality_profile_map: BiMap::from_iter([
|
||||
(2i64, "Lossless".to_owned()),
|
||||
(1i64, "Standard".to_owned()),
|
||||
]),
|
||||
metadata_profile_map: BiMap::from_iter([
|
||||
(2i64, "None".to_owned()),
|
||||
(1i64, "Standard".to_owned()),
|
||||
]),
|
||||
..ReadarrData::default()
|
||||
};
|
||||
|
||||
let add_root_folder_modal = AddReadarrRootFolderModal::from(&readarr_data);
|
||||
|
||||
assert_eq!(
|
||||
*add_root_folder_modal.monitor_list.current_selection(),
|
||||
MonitorType::default()
|
||||
);
|
||||
assert_eq!(
|
||||
*add_root_folder_modal
|
||||
.monitor_new_items_list
|
||||
.current_selection(),
|
||||
NewItemMonitorType::default()
|
||||
);
|
||||
assert_str_eq!(
|
||||
add_root_folder_modal
|
||||
.quality_profile_list
|
||||
.current_selection(),
|
||||
"Standard"
|
||||
);
|
||||
assert_str_eq!(
|
||||
add_root_folder_modal
|
||||
.metadata_profile_list
|
||||
.current_selection(),
|
||||
"Standard"
|
||||
);
|
||||
assert_is_empty!(add_root_folder_modal.name.text);
|
||||
assert_is_empty!(add_root_folder_modal.path.text);
|
||||
assert_is_empty!(add_root_folder_modal.tags.text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_book_details_modal_default() {
|
||||
let book_details_modal = BookDetailsModal::default();
|
||||
|
||||
assert_is_empty!(book_details_modal.editions);
|
||||
assert_none!(book_details_modal.edition_details_modal);
|
||||
assert_is_empty!(book_details_modal.book_files);
|
||||
assert_is_empty!(book_details_modal.book_releases);
|
||||
assert_is_empty!(book_details_modal.book_history);
|
||||
|
||||
assert_eq!(book_details_modal.book_details_tabs.tabs.len(), 3);
|
||||
|
||||
assert_str_eq!(
|
||||
book_details_modal.book_details_tabs.tabs[0].title,
|
||||
"Editions"
|
||||
);
|
||||
assert_eq!(
|
||||
book_details_modal.book_details_tabs.tabs[0].route,
|
||||
ActiveReadarrBlock::BookDetails.into()
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
&book_details_modal.book_details_tabs.tabs[0].contextual_help,
|
||||
&BOOK_DETAILS_CONTEXT_CLUES
|
||||
);
|
||||
assert_none!(book_details_modal.book_details_tabs.tabs[0].config);
|
||||
|
||||
assert_str_eq!(
|
||||
book_details_modal.book_details_tabs.tabs[1].title,
|
||||
"History"
|
||||
);
|
||||
assert_eq!(
|
||||
book_details_modal.book_details_tabs.tabs[1].route,
|
||||
ActiveReadarrBlock::BookHistory.into()
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
&book_details_modal.book_details_tabs.tabs[1].contextual_help,
|
||||
&BOOK_HISTORY_CONTEXT_CLUES
|
||||
);
|
||||
assert_none!(book_details_modal.book_details_tabs.tabs[1].config);
|
||||
|
||||
assert_str_eq!(
|
||||
book_details_modal.book_details_tabs.tabs[2].title,
|
||||
"Manual Search"
|
||||
);
|
||||
assert_eq!(
|
||||
book_details_modal.book_details_tabs.tabs[2].route,
|
||||
ActiveReadarrBlock::ManualBookSearch.into()
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
&book_details_modal.book_details_tabs.tabs[2].contextual_help,
|
||||
&MANUAL_BOOK_SEARCH_CONTEXT_CLUES
|
||||
);
|
||||
assert_none!(book_details_modal.book_details_tabs.tabs[2].config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edition_details_modal_default() {
|
||||
let edition_details_modal = EditionDetailsModal::default();
|
||||
|
||||
assert_is_empty!(edition_details_modal.edition_details);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,980 @@
|
||||
use serde_json::Number;
|
||||
|
||||
use super::modals::{AddAuthorModal, AddReadarrRootFolderModal, BookDetailsModal, EditAuthorModal};
|
||||
use crate::app::context_clues::{
|
||||
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
||||
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
||||
};
|
||||
use crate::app::readarr::readarr_context_clues::{
|
||||
AUTHOR_DETAILS_CONTEXT_CLUES, AUTHOR_HISTORY_CONTEXT_CLUES, AUTHORS_CONTEXT_CLUES,
|
||||
MANUAL_AUTHOR_SEARCH_CONTEXT_CLUES,
|
||||
};
|
||||
use crate::models::readarr_models::{BlocklistItem, ReadarrRelease, ReadarrTask};
|
||||
use crate::models::servarr_data::modals::EditIndexerModal;
|
||||
use crate::models::servarr_models::{IndexerSettings, QueueEvent};
|
||||
use crate::models::stateful_list::StatefulList;
|
||||
use crate::models::{
|
||||
BlockSelectionState, HorizontallyScrollableText, Route, ScrollableText, TabRoute, TabState,
|
||||
readarr_models::{AddAuthorSearchResult, Author, Book, DownloadRecord, ReadarrHistoryItem},
|
||||
servarr_data::modals::IndexerTestResultModalItem,
|
||||
servarr_models::{DiskSpace, Indexer, RootFolder},
|
||||
stateful_table::StatefulTable,
|
||||
};
|
||||
use crate::network::readarr_network::ReadarrEvent;
|
||||
use bimap::BiMap;
|
||||
use chrono::{DateTime, Utc};
|
||||
use itertools::Itertools;
|
||||
use strum::EnumIter;
|
||||
#[cfg(test)]
|
||||
use {
|
||||
super::modals::EditionDetailsModal,
|
||||
crate::models::readarr_models::{
|
||||
AuthorStatus, BookFile, DownloadStatus, Edition, MonitorType, NewItemMonitorType,
|
||||
ReadarrHistoryEventType, ReadarrTaskName,
|
||||
},
|
||||
crate::models::servarr_models::{IndexerField, Quality, QualityWrapper},
|
||||
crate::models::stateful_table::SortOption,
|
||||
crate::network::servarr_test_utils::{
|
||||
diskspace, indexer_settings, indexer_test_result, queued_event,
|
||||
},
|
||||
crate::network::sonarr_network::sonarr_network_test_utils::test_utils::updates,
|
||||
crate::sort_option,
|
||||
serde_json::json,
|
||||
strum::{Display, EnumString, IntoEnumIterator},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "readarr_data_tests.rs"]
|
||||
mod readarr_data_tests;
|
||||
|
||||
pub struct ReadarrData<'a> {
|
||||
pub add_author_modal: Option<AddAuthorModal>,
|
||||
pub add_author_search: Option<HorizontallyScrollableText>,
|
||||
pub add_import_list_exclusion: bool,
|
||||
pub add_root_folder_modal: Option<AddReadarrRootFolderModal>,
|
||||
pub add_searched_authors: Option<StatefulTable<AddAuthorSearchResult>>,
|
||||
pub author_history: StatefulTable<ReadarrHistoryItem>,
|
||||
pub author_info_tabs: TabState,
|
||||
pub author_releases: StatefulTable<ReadarrRelease>,
|
||||
pub authors: StatefulTable<Author>,
|
||||
pub blocklist: StatefulTable<BlocklistItem>,
|
||||
pub book_details_modal: Option<BookDetailsModal>,
|
||||
pub books: StatefulTable<Book>,
|
||||
pub delete_files: bool,
|
||||
pub disk_space_vec: Vec<DiskSpace>,
|
||||
pub downloads: StatefulTable<DownloadRecord>,
|
||||
pub edit_author_modal: Option<EditAuthorModal>,
|
||||
pub edit_indexer_modal: Option<EditIndexerModal>,
|
||||
pub history: StatefulTable<ReadarrHistoryItem>,
|
||||
pub indexer_settings: Option<IndexerSettings>,
|
||||
pub indexer_test_all_results: Option<StatefulTable<IndexerTestResultModalItem>>,
|
||||
pub indexer_test_errors: Option<String>,
|
||||
pub indexers: StatefulTable<Indexer>,
|
||||
pub log_details: StatefulList<HorizontallyScrollableText>,
|
||||
pub logs: StatefulList<HorizontallyScrollableText>,
|
||||
pub main_tabs: TabState,
|
||||
pub metadata_profile_map: BiMap<i64, String>,
|
||||
pub prompt_confirm: bool,
|
||||
pub prompt_confirm_action: Option<ReadarrEvent>,
|
||||
pub quality_profile_map: BiMap<i64, String>,
|
||||
pub queued_events: StatefulTable<QueueEvent>,
|
||||
pub root_folders: StatefulTable<RootFolder>,
|
||||
pub selected_block: BlockSelectionState<'a, ActiveReadarrBlock>,
|
||||
pub start_time: DateTime<Utc>,
|
||||
pub tags_map: BiMap<i64, String>,
|
||||
pub tasks: StatefulTable<ReadarrTask>,
|
||||
pub updates: ScrollableText,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl ReadarrData<'_> {
|
||||
pub fn reset_delete_preferences(&mut self) {
|
||||
self.delete_files = false;
|
||||
self.add_import_list_exclusion = false;
|
||||
}
|
||||
|
||||
pub fn reset_author_info_tabs(&mut self) {
|
||||
self.books = StatefulTable::default();
|
||||
self.author_releases = StatefulTable::default();
|
||||
self.author_history = StatefulTable::default();
|
||||
self.author_info_tabs.index = 0;
|
||||
}
|
||||
|
||||
pub fn tag_ids_to_display(&self, tag_ids: &[Number]) -> String {
|
||||
tag_ids
|
||||
.iter()
|
||||
.filter_map(|id| {
|
||||
let id = id.as_i64()?;
|
||||
self.tags_map.get_by_left(&id).cloned()
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
pub fn sorted_quality_profile_names(&self) -> Vec<String> {
|
||||
self
|
||||
.quality_profile_map
|
||||
.iter()
|
||||
.sorted_by_key(|(id, _)| *id)
|
||||
.map(|(_, name)| name)
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn sorted_metadata_profile_names(&self) -> Vec<String> {
|
||||
self
|
||||
.metadata_profile_map
|
||||
.iter()
|
||||
.sorted_by_key(|(id, _)| *id)
|
||||
.map(|(_, name)| name)
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Default for ReadarrData<'a> {
|
||||
fn default() -> ReadarrData<'a> {
|
||||
ReadarrData {
|
||||
add_author_modal: None,
|
||||
add_author_search: None,
|
||||
add_import_list_exclusion: false,
|
||||
add_root_folder_modal: None,
|
||||
add_searched_authors: None,
|
||||
author_history: StatefulTable::default(),
|
||||
author_releases: StatefulTable::default(),
|
||||
authors: StatefulTable::default(),
|
||||
blocklist: StatefulTable::default(),
|
||||
book_details_modal: None,
|
||||
books: StatefulTable::default(),
|
||||
delete_files: false,
|
||||
disk_space_vec: Vec::new(),
|
||||
downloads: StatefulTable::default(),
|
||||
edit_author_modal: None,
|
||||
edit_indexer_modal: None,
|
||||
history: StatefulTable::default(),
|
||||
indexer_settings: None,
|
||||
indexer_test_all_results: None,
|
||||
indexer_test_errors: None,
|
||||
indexers: StatefulTable::default(),
|
||||
log_details: StatefulList::default(),
|
||||
logs: StatefulList::default(),
|
||||
metadata_profile_map: BiMap::new(),
|
||||
prompt_confirm: false,
|
||||
prompt_confirm_action: None,
|
||||
quality_profile_map: BiMap::new(),
|
||||
queued_events: StatefulTable::default(),
|
||||
root_folders: StatefulTable::default(),
|
||||
selected_block: BlockSelectionState::default(),
|
||||
start_time: DateTime::default(),
|
||||
tags_map: BiMap::new(),
|
||||
tasks: StatefulTable::default(),
|
||||
updates: ScrollableText::default(),
|
||||
version: String::new(),
|
||||
main_tabs: TabState::new(vec![
|
||||
TabRoute {
|
||||
title: "Library".to_string(),
|
||||
route: ActiveReadarrBlock::Authors.into(),
|
||||
contextual_help: Some(&AUTHORS_CONTEXT_CLUES),
|
||||
config: None,
|
||||
},
|
||||
TabRoute {
|
||||
title: "Downloads".to_string(),
|
||||
route: ActiveReadarrBlock::Downloads.into(),
|
||||
contextual_help: Some(&DOWNLOADS_CONTEXT_CLUES),
|
||||
config: None,
|
||||
},
|
||||
TabRoute {
|
||||
title: "Blocklist".to_string(),
|
||||
route: ActiveReadarrBlock::Blocklist.into(),
|
||||
contextual_help: Some(&BLOCKLIST_CONTEXT_CLUES),
|
||||
config: None,
|
||||
},
|
||||
TabRoute {
|
||||
title: "History".to_string(),
|
||||
route: ActiveReadarrBlock::History.into(),
|
||||
contextual_help: Some(&HISTORY_CONTEXT_CLUES),
|
||||
config: None,
|
||||
},
|
||||
TabRoute {
|
||||
title: "Root Folders".to_string(),
|
||||
route: ActiveReadarrBlock::RootFolders.into(),
|
||||
contextual_help: Some(&ROOT_FOLDERS_CONTEXT_CLUES),
|
||||
config: None,
|
||||
},
|
||||
TabRoute {
|
||||
title: "Indexers".to_string(),
|
||||
route: ActiveReadarrBlock::Indexers.into(),
|
||||
contextual_help: Some(&INDEXERS_CONTEXT_CLUES),
|
||||
config: None,
|
||||
},
|
||||
TabRoute {
|
||||
title: "System".to_string(),
|
||||
route: ActiveReadarrBlock::System.into(),
|
||||
contextual_help: Some(&SYSTEM_CONTEXT_CLUES),
|
||||
config: None,
|
||||
},
|
||||
]),
|
||||
author_info_tabs: TabState::new(vec![
|
||||
TabRoute {
|
||||
title: "Books".to_string(),
|
||||
route: ActiveReadarrBlock::AuthorDetails.into(),
|
||||
contextual_help: Some(&AUTHOR_DETAILS_CONTEXT_CLUES),
|
||||
config: None,
|
||||
},
|
||||
TabRoute {
|
||||
title: "History".to_string(),
|
||||
route: ActiveReadarrBlock::AuthorHistory.into(),
|
||||
contextual_help: Some(&AUTHOR_HISTORY_CONTEXT_CLUES),
|
||||
config: None,
|
||||
},
|
||||
TabRoute {
|
||||
title: "Author Search".to_string(),
|
||||
route: ActiveReadarrBlock::ManualAuthorSearch.into(),
|
||||
contextual_help: Some(&MANUAL_AUTHOR_SEARCH_CONTEXT_CLUES),
|
||||
config: None,
|
||||
},
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl ReadarrData<'_> {
|
||||
pub fn test_default_fully_populated() -> Self {
|
||||
let quality_profile_map = BiMap::from_iter([(1i64, "Standard".to_owned())]);
|
||||
let metadata_profile_map = BiMap::from_iter([(1i64, "Standard".to_owned())]);
|
||||
let tags_map = BiMap::from_iter([(1i64, "alex".to_owned())]);
|
||||
let root_folder = RootFolder {
|
||||
id: 1,
|
||||
path: "/nfs".to_owned(),
|
||||
accessible: true,
|
||||
free_space: 219902325555200,
|
||||
unmapped_folders: None,
|
||||
};
|
||||
let quality = QualityWrapper {
|
||||
quality: Quality {
|
||||
name: "EPUB".to_owned(),
|
||||
},
|
||||
};
|
||||
let date = DateTime::from(DateTime::parse_from_rfc3339("2023-05-20T21:29:16Z").unwrap());
|
||||
let author = Author {
|
||||
id: 1,
|
||||
author_name: "Test Author".into(),
|
||||
foreign_author_id: "test-foreign-id".to_owned(),
|
||||
status: AuthorStatus::Continuing,
|
||||
overview: Some("some interesting description of the author".to_owned()),
|
||||
path: "/nfs/books/Test Author".to_owned(),
|
||||
quality_profile_id: 1,
|
||||
metadata_profile_id: 1,
|
||||
monitored: true,
|
||||
monitor_new_items: NewItemMonitorType::All,
|
||||
genres: vec!["science fiction".to_owned()],
|
||||
tags: vec![Number::from(1)],
|
||||
..Author::default()
|
||||
};
|
||||
let book = Book {
|
||||
id: 1,
|
||||
title: "Test Book".into(),
|
||||
author_id: 1,
|
||||
foreign_book_id: "test-foreign-book-id".to_owned(),
|
||||
monitored: true,
|
||||
any_edition_ok: true,
|
||||
page_count: Some(288),
|
||||
release_date: Some(date),
|
||||
..Book::default()
|
||||
};
|
||||
let readarr_history_item = ReadarrHistoryItem {
|
||||
id: 1,
|
||||
author_id: 1,
|
||||
book_id: 1,
|
||||
source_title: "Test Source Title".into(),
|
||||
quality: quality.clone(),
|
||||
date,
|
||||
event_type: ReadarrHistoryEventType::Grabbed,
|
||||
..ReadarrHistoryItem::default()
|
||||
};
|
||||
let torrent_release = ReadarrRelease {
|
||||
guid: "1234".to_owned(),
|
||||
protocol: "torrent".to_owned(),
|
||||
age: 1,
|
||||
title: "Test Torrent Release".into(),
|
||||
author_name: Some("Test Author".to_owned()),
|
||||
book_title: Some("Test Book".to_owned()),
|
||||
indexer: "kickass torrents".to_owned(),
|
||||
indexer_id: 1,
|
||||
size: 1234,
|
||||
rejected: true,
|
||||
rejections: Some(vec!["Unknown quality profile".to_owned()]),
|
||||
seeders: Some(Number::from(2)),
|
||||
leechers: Some(Number::from(1)),
|
||||
quality: quality.clone(),
|
||||
};
|
||||
let usenet_release = ReadarrRelease {
|
||||
guid: "5678".to_owned(),
|
||||
protocol: "usenet".to_owned(),
|
||||
title: "Test Usenet Release".into(),
|
||||
indexer: "DrunkenSlug".to_owned(),
|
||||
indexer_id: 2,
|
||||
seeders: None,
|
||||
leechers: None,
|
||||
..torrent_release.clone()
|
||||
};
|
||||
let blocklist_item = BlocklistItem {
|
||||
id: 1,
|
||||
author_id: 1,
|
||||
book_ids: Some(vec![Number::from(1)]),
|
||||
source_title: "Test Source Title".to_owned(),
|
||||
quality: quality.clone(),
|
||||
date,
|
||||
protocol: "usenet".to_owned(),
|
||||
indexer: "DrunkenSlug".to_owned(),
|
||||
message: "test message".to_owned(),
|
||||
author: author.clone(),
|
||||
};
|
||||
let download_record = DownloadRecord {
|
||||
title: "Test Download Title".to_owned(),
|
||||
status: DownloadStatus::Downloading,
|
||||
id: 1,
|
||||
book_id: Some(Number::from(1)),
|
||||
author_id: Some(Number::from(1)),
|
||||
size: 3543348019f64,
|
||||
sizeleft: 1771674009f64,
|
||||
output_path: Some("/nfs/books/Test Author".into()),
|
||||
indexer: "kickass torrents".to_owned(),
|
||||
download_client: Some("transmission".to_owned()),
|
||||
};
|
||||
let indexer = Indexer {
|
||||
id: 1,
|
||||
name: Some("Test Indexer".to_owned()),
|
||||
implementation: Some("Torznab".to_owned()),
|
||||
implementation_name: Some("Torznab".to_owned()),
|
||||
config_contract: Some("TorznabSettings".to_owned()),
|
||||
supports_rss: true,
|
||||
supports_search: true,
|
||||
enable_rss: true,
|
||||
enable_automatic_search: true,
|
||||
enable_interactive_search: true,
|
||||
protocol: "torrent".to_owned(),
|
||||
priority: 25,
|
||||
download_client_id: 0,
|
||||
tags: vec![Number::from(1)],
|
||||
fields: Some(vec![
|
||||
IndexerField {
|
||||
name: Some("baseUrl".to_owned()),
|
||||
value: Some(json!("https://test.com")),
|
||||
},
|
||||
IndexerField {
|
||||
name: Some("apiKey".to_owned()),
|
||||
value: Some(json!("")),
|
||||
},
|
||||
IndexerField {
|
||||
name: Some("seedCriteria.seedRatio".to_owned()),
|
||||
value: Some(json!("1.2")),
|
||||
},
|
||||
]),
|
||||
};
|
||||
let add_author_search_result = AddAuthorSearchResult {
|
||||
foreign_author_id: "test-foreign-id".to_owned(),
|
||||
author_name: "Test Author".into(),
|
||||
status: AuthorStatus::Continuing,
|
||||
overview: Some("some interesting description of the author".to_owned()),
|
||||
genres: vec!["science fiction".to_owned()],
|
||||
..AddAuthorSearchResult::default()
|
||||
};
|
||||
let task = ReadarrTask {
|
||||
name: "Backup".to_owned(),
|
||||
task_name: ReadarrTaskName::Backup,
|
||||
interval: 60,
|
||||
last_execution: date,
|
||||
last_duration: "00:00:00.5111547".to_owned(),
|
||||
next_execution: DateTime::from(DateTime::parse_from_rfc3339("2023-05-20T22:29:16Z").unwrap()),
|
||||
};
|
||||
let log_line = "2025-12-16 16:40:59 UTC|INFO|ImportListSyncService|No list items to process";
|
||||
|
||||
let mut add_author_modal = AddAuthorModal {
|
||||
tags: "usenet, testing".into(),
|
||||
..AddAuthorModal::default()
|
||||
};
|
||||
add_author_modal
|
||||
.monitor_list
|
||||
.set_items(Vec::from_iter(MonitorType::iter()));
|
||||
add_author_modal
|
||||
.monitor_new_items_list
|
||||
.set_items(Vec::from_iter(NewItemMonitorType::iter()));
|
||||
add_author_modal
|
||||
.metadata_profile_list
|
||||
.set_items(vec!["Standard".to_owned()]);
|
||||
add_author_modal
|
||||
.quality_profile_list
|
||||
.set_items(vec!["Standard".to_owned()]);
|
||||
add_author_modal
|
||||
.root_folder_list
|
||||
.set_items(vec![root_folder.clone()]);
|
||||
|
||||
let mut edit_author_modal = EditAuthorModal {
|
||||
monitored: Some(true),
|
||||
path: "/nfs/books".into(),
|
||||
tags: "alex".into(),
|
||||
..EditAuthorModal::default()
|
||||
};
|
||||
edit_author_modal
|
||||
.monitor_list
|
||||
.set_items(NewItemMonitorType::iter().collect());
|
||||
edit_author_modal
|
||||
.quality_profile_list
|
||||
.set_items(vec!["Standard".to_owned()]);
|
||||
edit_author_modal
|
||||
.metadata_profile_list
|
||||
.set_items(vec!["Standard".to_owned()]);
|
||||
|
||||
let mut add_root_folder_modal = AddReadarrRootFolderModal {
|
||||
name: "Test Root Folder".into(),
|
||||
path: "/nfs/books".into(),
|
||||
tags: "test".into(),
|
||||
..AddReadarrRootFolderModal::default()
|
||||
};
|
||||
add_root_folder_modal
|
||||
.monitor_list
|
||||
.set_items(Vec::from_iter(MonitorType::iter()));
|
||||
add_root_folder_modal
|
||||
.monitor_new_items_list
|
||||
.set_items(Vec::from_iter(NewItemMonitorType::iter()));
|
||||
add_root_folder_modal
|
||||
.quality_profile_list
|
||||
.set_items(vec!["Standard".to_owned()]);
|
||||
add_root_folder_modal
|
||||
.metadata_profile_list
|
||||
.set_items(vec!["Standard".to_owned()]);
|
||||
|
||||
let edition_details_modal = EditionDetailsModal {
|
||||
edition_details: ScrollableText::with_string("Some details".to_owned()),
|
||||
};
|
||||
|
||||
let mut book_details_modal = BookDetailsModal {
|
||||
edition_details_modal: Some(edition_details_modal),
|
||||
..BookDetailsModal::default()
|
||||
};
|
||||
book_details_modal.editions.set_items(vec![Edition {
|
||||
id: 1,
|
||||
book_id: 1,
|
||||
foreign_edition_id: "test-foreign-edition-id".to_owned(),
|
||||
monitored: true,
|
||||
is_ebook: true,
|
||||
title: "Test Edition".to_owned(),
|
||||
page_count: Some(288),
|
||||
release_date: Some(date),
|
||||
..Edition::default()
|
||||
}]);
|
||||
book_details_modal.editions.search = Some("edition search".into());
|
||||
book_details_modal.book_files.set_items(vec![BookFile {
|
||||
id: 1,
|
||||
author_id: 1,
|
||||
book_id: 1,
|
||||
path: "/nfs/books/Test Author/Test Book.epub".to_owned(),
|
||||
size: 3543348019,
|
||||
date_added: date,
|
||||
quality: quality.clone(),
|
||||
media_info: None,
|
||||
quality_cutoff_not_met: false,
|
||||
}]);
|
||||
book_details_modal
|
||||
.book_history
|
||||
.set_items(vec![readarr_history_item.clone()]);
|
||||
book_details_modal.book_history.search = Some("book history search".into());
|
||||
book_details_modal.book_history.filter = Some("book history filter".into());
|
||||
book_details_modal
|
||||
.book_history
|
||||
.sorting(vec![sort_option!(id)]);
|
||||
book_details_modal
|
||||
.book_releases
|
||||
.set_items(vec![torrent_release.clone(), usenet_release.clone()]);
|
||||
book_details_modal
|
||||
.book_releases
|
||||
.sorting(vec![sort_option!(indexer_id)]);
|
||||
|
||||
let edit_indexer_modal = EditIndexerModal {
|
||||
name: "DrunkenSlug".into(),
|
||||
enable_rss: Some(true),
|
||||
enable_automatic_search: Some(true),
|
||||
enable_interactive_search: Some(true),
|
||||
url: "http://127.0.0.1:9696/1/".into(),
|
||||
api_key: "someApiKey".into(),
|
||||
seed_ratio: "ratio".into(),
|
||||
tags: "25".into(),
|
||||
priority: 1,
|
||||
};
|
||||
|
||||
let mut indexer_test_all_results = StatefulTable::default();
|
||||
indexer_test_all_results.set_items(vec![indexer_test_result()]);
|
||||
|
||||
let mut readarr_data = ReadarrData {
|
||||
add_author_modal: Some(add_author_modal),
|
||||
add_root_folder_modal: Some(add_root_folder_modal),
|
||||
book_details_modal: Some(book_details_modal),
|
||||
delete_files: true,
|
||||
disk_space_vec: vec![diskspace()],
|
||||
edit_author_modal: Some(edit_author_modal),
|
||||
edit_indexer_modal: Some(edit_indexer_modal),
|
||||
indexer_settings: Some(indexer_settings()),
|
||||
indexer_test_all_results: Some(indexer_test_all_results),
|
||||
indexer_test_errors: Some("error".to_string()),
|
||||
metadata_profile_map,
|
||||
quality_profile_map,
|
||||
start_time: date,
|
||||
tags_map,
|
||||
updates: updates(),
|
||||
version: "1.2.3.4".to_owned(),
|
||||
..ReadarrData::default()
|
||||
};
|
||||
readarr_data
|
||||
.author_history
|
||||
.set_items(vec![readarr_history_item.clone()]);
|
||||
readarr_data.author_history.sorting(vec![sort_option!(id)]);
|
||||
readarr_data.author_history.search = Some("author history search".into());
|
||||
readarr_data.author_history.filter = Some("author history filter".into());
|
||||
readarr_data
|
||||
.author_releases
|
||||
.set_items(vec![torrent_release, usenet_release]);
|
||||
readarr_data
|
||||
.author_releases
|
||||
.sorting(vec![sort_option!(indexer_id)]);
|
||||
readarr_data.authors.set_items(vec![author]);
|
||||
readarr_data.authors.sorting(vec![SortOption {
|
||||
name: "Name",
|
||||
cmp_fn: Some(|a: &Author, b: &Author| a.author_name.text.cmp(&b.author_name.text)),
|
||||
}]);
|
||||
readarr_data.authors.search = Some("author search".into());
|
||||
readarr_data.authors.filter = Some("author filter".into());
|
||||
readarr_data.blocklist.set_items(vec![blocklist_item]);
|
||||
readarr_data.blocklist.sorting(vec![sort_option!(id)]);
|
||||
readarr_data.books.set_items(vec![book]);
|
||||
readarr_data.books.search = Some("book search".into());
|
||||
readarr_data.downloads.set_items(vec![download_record]);
|
||||
readarr_data.history.set_items(vec![readarr_history_item]);
|
||||
readarr_data.history.sorting(vec![SortOption {
|
||||
name: "Date",
|
||||
cmp_fn: Some(|a: &ReadarrHistoryItem, b: &ReadarrHistoryItem| a.date.cmp(&b.date)),
|
||||
}]);
|
||||
readarr_data.history.search = Some("test search".into());
|
||||
readarr_data.history.filter = Some("test filter".into());
|
||||
readarr_data.indexers.set_items(vec![indexer]);
|
||||
readarr_data.root_folders.set_items(vec![root_folder]);
|
||||
readarr_data.queued_events.set_items(vec![queued_event()]);
|
||||
readarr_data.add_author_search = Some("Test Author".into());
|
||||
let mut add_searched_authors = StatefulTable::default();
|
||||
add_searched_authors.set_items(vec![add_author_search_result]);
|
||||
readarr_data.add_searched_authors = Some(add_searched_authors);
|
||||
readarr_data.logs.set_items(vec![log_line.into()]);
|
||||
readarr_data.log_details.set_items(vec![log_line.into()]);
|
||||
readarr_data.tasks.set_items(vec![task]);
|
||||
|
||||
readarr_data
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, EnumIter)]
|
||||
#[cfg_attr(test, derive(Display, EnumString))]
|
||||
pub enum ActiveReadarrBlock {
|
||||
#[default]
|
||||
Authors,
|
||||
AddAuthorAlreadyInLibrary,
|
||||
AddAuthorConfirmPrompt,
|
||||
AddAuthorEmptySearchResults,
|
||||
AddAuthorPrompt,
|
||||
AddAuthorSearchInput,
|
||||
AddAuthorSearchResults,
|
||||
AddAuthorSelectMetadataProfile,
|
||||
AddAuthorSelectMonitor,
|
||||
AddAuthorSelectMonitorNewItems,
|
||||
AddAuthorSelectQualityProfile,
|
||||
AddAuthorSelectRootFolder,
|
||||
AddAuthorTagsInput,
|
||||
AddRootFolderConfirmPrompt,
|
||||
AddRootFolderNameInput,
|
||||
AddRootFolderPathInput,
|
||||
AddRootFolderPrompt,
|
||||
AddRootFolderSelectMetadataProfile,
|
||||
AddRootFolderSelectMonitor,
|
||||
AddRootFolderSelectMonitorNewItems,
|
||||
AddRootFolderSelectQualityProfile,
|
||||
AddRootFolderTagsInput,
|
||||
AllIndexerSettingsPrompt,
|
||||
AuthorDetails,
|
||||
AuthorHistory,
|
||||
AuthorHistoryDetails,
|
||||
AuthorHistorySortPrompt,
|
||||
AuthorsSortPrompt,
|
||||
AutomaticallySearchAuthorPrompt,
|
||||
AutomaticallySearchBookPrompt,
|
||||
Blocklist,
|
||||
BlocklistClearAllItemsPrompt,
|
||||
BlocklistItemDetails,
|
||||
BlocklistSortPrompt,
|
||||
BookDetails,
|
||||
BookEditionDetails,
|
||||
BookFileInfo,
|
||||
BookHistory,
|
||||
BookHistoryDetails,
|
||||
BookHistorySortPrompt,
|
||||
DeleteAuthorConfirmPrompt,
|
||||
DeleteAuthorPrompt,
|
||||
DeleteAuthorToggleAddListExclusion,
|
||||
DeleteAuthorToggleDeleteFile,
|
||||
DeleteBlocklistItemPrompt,
|
||||
DeleteBookConfirmPrompt,
|
||||
DeleteBookFilePrompt,
|
||||
DeleteBookPrompt,
|
||||
DeleteBookToggleAddListExclusion,
|
||||
DeleteBookToggleDeleteFile,
|
||||
DeleteDownloadPrompt,
|
||||
DeleteIndexerPrompt,
|
||||
DeleteRootFolderPrompt,
|
||||
Downloads,
|
||||
EditAuthorConfirmPrompt,
|
||||
EditAuthorPathInput,
|
||||
EditAuthorPrompt,
|
||||
EditAuthorSelectMetadataProfile,
|
||||
EditAuthorSelectMonitorNewItems,
|
||||
EditAuthorSelectQualityProfile,
|
||||
EditAuthorTagsInput,
|
||||
EditAuthorToggleMonitored,
|
||||
EditIndexerApiKeyInput,
|
||||
EditIndexerConfirmPrompt,
|
||||
EditIndexerNameInput,
|
||||
EditIndexerPriorityInput,
|
||||
EditIndexerPrompt,
|
||||
EditIndexerSeedRatioInput,
|
||||
EditIndexerTagsInput,
|
||||
EditIndexerToggleEnableAutomaticSearch,
|
||||
EditIndexerToggleEnableInteractiveSearch,
|
||||
EditIndexerToggleEnableRss,
|
||||
EditIndexerUrlInput,
|
||||
FilterAuthorHistory,
|
||||
FilterAuthorHistoryError,
|
||||
FilterAuthors,
|
||||
FilterAuthorsError,
|
||||
FilterBookHistory,
|
||||
FilterBookHistoryError,
|
||||
FilterHistory,
|
||||
FilterHistoryError,
|
||||
History,
|
||||
HistoryItemDetails,
|
||||
HistorySortPrompt,
|
||||
Indexers,
|
||||
IndexerSettingsConfirmPrompt,
|
||||
IndexerSettingsMaximumSizeInput,
|
||||
IndexerSettingsMinimumAgeInput,
|
||||
IndexerSettingsRetentionInput,
|
||||
IndexerSettingsRssSyncIntervalInput,
|
||||
ManualAuthorSearch,
|
||||
ManualAuthorSearchConfirmPrompt,
|
||||
ManualAuthorSearchSortPrompt,
|
||||
ManualBookSearch,
|
||||
ManualBookSearchConfirmPrompt,
|
||||
ManualBookSearchSortPrompt,
|
||||
RootFolders,
|
||||
SearchAuthorHistory,
|
||||
SearchAuthorHistoryError,
|
||||
SearchAuthors,
|
||||
SearchAuthorsError,
|
||||
SearchBookHistory,
|
||||
SearchBookHistoryError,
|
||||
SearchBooks,
|
||||
SearchBooksError,
|
||||
SearchEditions,
|
||||
SearchEditionsError,
|
||||
SearchHistory,
|
||||
SearchHistoryError,
|
||||
System,
|
||||
SystemLogs,
|
||||
SystemQueuedEvents,
|
||||
SystemTasks,
|
||||
SystemTaskStartConfirmPrompt,
|
||||
SystemUpdates,
|
||||
TestAllIndexers,
|
||||
TestIndexer,
|
||||
UpdateAllAuthorsPrompt,
|
||||
UpdateAndScanAuthorPrompt,
|
||||
UpdateDownloadsPrompt,
|
||||
}
|
||||
|
||||
pub static LIBRARY_BLOCKS: [ActiveReadarrBlock; 7] = [
|
||||
ActiveReadarrBlock::Authors,
|
||||
ActiveReadarrBlock::AuthorsSortPrompt,
|
||||
ActiveReadarrBlock::FilterAuthors,
|
||||
ActiveReadarrBlock::FilterAuthorsError,
|
||||
ActiveReadarrBlock::SearchAuthors,
|
||||
ActiveReadarrBlock::SearchAuthorsError,
|
||||
ActiveReadarrBlock::UpdateAllAuthorsPrompt,
|
||||
];
|
||||
|
||||
pub static AUTHOR_DETAILS_BLOCKS: [ActiveReadarrBlock; 15] = [
|
||||
ActiveReadarrBlock::AuthorDetails,
|
||||
ActiveReadarrBlock::AuthorHistory,
|
||||
ActiveReadarrBlock::AuthorHistoryDetails,
|
||||
ActiveReadarrBlock::AuthorHistorySortPrompt,
|
||||
ActiveReadarrBlock::AutomaticallySearchAuthorPrompt,
|
||||
ActiveReadarrBlock::FilterAuthorHistory,
|
||||
ActiveReadarrBlock::FilterAuthorHistoryError,
|
||||
ActiveReadarrBlock::ManualAuthorSearch,
|
||||
ActiveReadarrBlock::ManualAuthorSearchConfirmPrompt,
|
||||
ActiveReadarrBlock::ManualAuthorSearchSortPrompt,
|
||||
ActiveReadarrBlock::SearchBooks,
|
||||
ActiveReadarrBlock::SearchBooksError,
|
||||
ActiveReadarrBlock::SearchAuthorHistory,
|
||||
ActiveReadarrBlock::SearchAuthorHistoryError,
|
||||
ActiveReadarrBlock::UpdateAndScanAuthorPrompt,
|
||||
];
|
||||
|
||||
pub static BOOK_DETAILS_BLOCKS: [ActiveReadarrBlock; 16] = [
|
||||
ActiveReadarrBlock::BookDetails,
|
||||
ActiveReadarrBlock::BookHistory,
|
||||
ActiveReadarrBlock::BookHistoryDetails,
|
||||
ActiveReadarrBlock::BookHistorySortPrompt,
|
||||
ActiveReadarrBlock::AutomaticallySearchBookPrompt,
|
||||
ActiveReadarrBlock::DeleteBookFilePrompt,
|
||||
ActiveReadarrBlock::FilterBookHistory,
|
||||
ActiveReadarrBlock::FilterBookHistoryError,
|
||||
ActiveReadarrBlock::BookFileInfo,
|
||||
ActiveReadarrBlock::ManualBookSearch,
|
||||
ActiveReadarrBlock::ManualBookSearchConfirmPrompt,
|
||||
ActiveReadarrBlock::ManualBookSearchSortPrompt,
|
||||
ActiveReadarrBlock::SearchBookHistory,
|
||||
ActiveReadarrBlock::SearchBookHistoryError,
|
||||
ActiveReadarrBlock::SearchEditions,
|
||||
ActiveReadarrBlock::SearchEditionsError,
|
||||
];
|
||||
|
||||
pub static EDITION_DETAILS_BLOCKS: [ActiveReadarrBlock; 1] =
|
||||
[ActiveReadarrBlock::BookEditionDetails];
|
||||
|
||||
pub static ADD_AUTHOR_BLOCKS: [ActiveReadarrBlock; 12] = [
|
||||
ActiveReadarrBlock::AddAuthorAlreadyInLibrary,
|
||||
ActiveReadarrBlock::AddAuthorConfirmPrompt,
|
||||
ActiveReadarrBlock::AddAuthorEmptySearchResults,
|
||||
ActiveReadarrBlock::AddAuthorPrompt,
|
||||
ActiveReadarrBlock::AddAuthorSearchInput,
|
||||
ActiveReadarrBlock::AddAuthorSearchResults,
|
||||
ActiveReadarrBlock::AddAuthorSelectMetadataProfile,
|
||||
ActiveReadarrBlock::AddAuthorSelectMonitor,
|
||||
ActiveReadarrBlock::AddAuthorSelectMonitorNewItems,
|
||||
ActiveReadarrBlock::AddAuthorSelectQualityProfile,
|
||||
ActiveReadarrBlock::AddAuthorSelectRootFolder,
|
||||
ActiveReadarrBlock::AddAuthorTagsInput,
|
||||
];
|
||||
|
||||
pub const ADD_AUTHOR_SELECTION_BLOCKS: &[&[ActiveReadarrBlock]] = &[
|
||||
&[ActiveReadarrBlock::AddAuthorSelectRootFolder],
|
||||
&[ActiveReadarrBlock::AddAuthorSelectMonitor],
|
||||
&[ActiveReadarrBlock::AddAuthorSelectMonitorNewItems],
|
||||
&[ActiveReadarrBlock::AddAuthorSelectQualityProfile],
|
||||
&[ActiveReadarrBlock::AddAuthorSelectMetadataProfile],
|
||||
&[ActiveReadarrBlock::AddAuthorTagsInput],
|
||||
&[ActiveReadarrBlock::AddAuthorConfirmPrompt],
|
||||
];
|
||||
|
||||
pub static DELETE_AUTHOR_BLOCKS: [ActiveReadarrBlock; 4] = [
|
||||
ActiveReadarrBlock::DeleteAuthorPrompt,
|
||||
ActiveReadarrBlock::DeleteAuthorConfirmPrompt,
|
||||
ActiveReadarrBlock::DeleteAuthorToggleDeleteFile,
|
||||
ActiveReadarrBlock::DeleteAuthorToggleAddListExclusion,
|
||||
];
|
||||
|
||||
pub const DELETE_AUTHOR_SELECTION_BLOCKS: &[&[ActiveReadarrBlock]] = &[
|
||||
&[ActiveReadarrBlock::DeleteAuthorToggleDeleteFile],
|
||||
&[ActiveReadarrBlock::DeleteAuthorToggleAddListExclusion],
|
||||
&[ActiveReadarrBlock::DeleteAuthorConfirmPrompt],
|
||||
];
|
||||
|
||||
pub static DELETE_BOOK_BLOCKS: [ActiveReadarrBlock; 4] = [
|
||||
ActiveReadarrBlock::DeleteBookPrompt,
|
||||
ActiveReadarrBlock::DeleteBookConfirmPrompt,
|
||||
ActiveReadarrBlock::DeleteBookToggleDeleteFile,
|
||||
ActiveReadarrBlock::DeleteBookToggleAddListExclusion,
|
||||
];
|
||||
|
||||
pub const DELETE_BOOK_SELECTION_BLOCKS: &[&[ActiveReadarrBlock]] = &[
|
||||
&[ActiveReadarrBlock::DeleteBookToggleDeleteFile],
|
||||
&[ActiveReadarrBlock::DeleteBookToggleAddListExclusion],
|
||||
&[ActiveReadarrBlock::DeleteBookConfirmPrompt],
|
||||
];
|
||||
|
||||
pub static EDIT_AUTHOR_BLOCKS: [ActiveReadarrBlock; 8] = [
|
||||
ActiveReadarrBlock::EditAuthorPrompt,
|
||||
ActiveReadarrBlock::EditAuthorConfirmPrompt,
|
||||
ActiveReadarrBlock::EditAuthorPathInput,
|
||||
ActiveReadarrBlock::EditAuthorSelectMetadataProfile,
|
||||
ActiveReadarrBlock::EditAuthorSelectMonitorNewItems,
|
||||
ActiveReadarrBlock::EditAuthorSelectQualityProfile,
|
||||
ActiveReadarrBlock::EditAuthorTagsInput,
|
||||
ActiveReadarrBlock::EditAuthorToggleMonitored,
|
||||
];
|
||||
|
||||
pub const EDIT_AUTHOR_SELECTION_BLOCKS: &[&[ActiveReadarrBlock]] = &[
|
||||
&[ActiveReadarrBlock::EditAuthorToggleMonitored],
|
||||
&[ActiveReadarrBlock::EditAuthorSelectMonitorNewItems],
|
||||
&[ActiveReadarrBlock::EditAuthorSelectQualityProfile],
|
||||
&[ActiveReadarrBlock::EditAuthorSelectMetadataProfile],
|
||||
&[ActiveReadarrBlock::EditAuthorPathInput],
|
||||
&[ActiveReadarrBlock::EditAuthorTagsInput],
|
||||
&[ActiveReadarrBlock::EditAuthorConfirmPrompt],
|
||||
];
|
||||
|
||||
pub static ADD_ROOT_FOLDER_BLOCKS: [ActiveReadarrBlock; 9] = [
|
||||
ActiveReadarrBlock::AddRootFolderPrompt,
|
||||
ActiveReadarrBlock::AddRootFolderConfirmPrompt,
|
||||
ActiveReadarrBlock::AddRootFolderNameInput,
|
||||
ActiveReadarrBlock::AddRootFolderPathInput,
|
||||
ActiveReadarrBlock::AddRootFolderSelectMonitor,
|
||||
ActiveReadarrBlock::AddRootFolderSelectMonitorNewItems,
|
||||
ActiveReadarrBlock::AddRootFolderSelectQualityProfile,
|
||||
ActiveReadarrBlock::AddRootFolderSelectMetadataProfile,
|
||||
ActiveReadarrBlock::AddRootFolderTagsInput,
|
||||
];
|
||||
|
||||
pub const ADD_ROOT_FOLDER_SELECTION_BLOCKS: &[&[ActiveReadarrBlock]] = &[
|
||||
&[ActiveReadarrBlock::AddRootFolderNameInput],
|
||||
&[ActiveReadarrBlock::AddRootFolderPathInput],
|
||||
&[ActiveReadarrBlock::AddRootFolderSelectMonitor],
|
||||
&[ActiveReadarrBlock::AddRootFolderSelectMonitorNewItems],
|
||||
&[ActiveReadarrBlock::AddRootFolderSelectQualityProfile],
|
||||
&[ActiveReadarrBlock::AddRootFolderSelectMetadataProfile],
|
||||
&[ActiveReadarrBlock::AddRootFolderTagsInput],
|
||||
&[ActiveReadarrBlock::AddRootFolderConfirmPrompt],
|
||||
];
|
||||
|
||||
pub static ROOT_FOLDERS_BLOCKS: [ActiveReadarrBlock; 2] = [
|
||||
ActiveReadarrBlock::RootFolders,
|
||||
ActiveReadarrBlock::DeleteRootFolderPrompt,
|
||||
];
|
||||
|
||||
pub static BLOCKLIST_BLOCKS: [ActiveReadarrBlock; 5] = [
|
||||
ActiveReadarrBlock::Blocklist,
|
||||
ActiveReadarrBlock::BlocklistItemDetails,
|
||||
ActiveReadarrBlock::DeleteBlocklistItemPrompt,
|
||||
ActiveReadarrBlock::BlocklistClearAllItemsPrompt,
|
||||
ActiveReadarrBlock::BlocklistSortPrompt,
|
||||
];
|
||||
|
||||
pub static DOWNLOADS_BLOCKS: [ActiveReadarrBlock; 3] = [
|
||||
ActiveReadarrBlock::Downloads,
|
||||
ActiveReadarrBlock::DeleteDownloadPrompt,
|
||||
ActiveReadarrBlock::UpdateDownloadsPrompt,
|
||||
];
|
||||
|
||||
pub static HISTORY_BLOCKS: [ActiveReadarrBlock; 7] = [
|
||||
ActiveReadarrBlock::History,
|
||||
ActiveReadarrBlock::HistoryItemDetails,
|
||||
ActiveReadarrBlock::HistorySortPrompt,
|
||||
ActiveReadarrBlock::SearchHistory,
|
||||
ActiveReadarrBlock::SearchHistoryError,
|
||||
ActiveReadarrBlock::FilterHistory,
|
||||
ActiveReadarrBlock::FilterHistoryError,
|
||||
];
|
||||
|
||||
pub static EDIT_INDEXER_BLOCKS: [ActiveReadarrBlock; 11] = [
|
||||
ActiveReadarrBlock::EditIndexerPrompt,
|
||||
ActiveReadarrBlock::EditIndexerConfirmPrompt,
|
||||
ActiveReadarrBlock::EditIndexerApiKeyInput,
|
||||
ActiveReadarrBlock::EditIndexerNameInput,
|
||||
ActiveReadarrBlock::EditIndexerSeedRatioInput,
|
||||
ActiveReadarrBlock::EditIndexerToggleEnableRss,
|
||||
ActiveReadarrBlock::EditIndexerToggleEnableAutomaticSearch,
|
||||
ActiveReadarrBlock::EditIndexerToggleEnableInteractiveSearch,
|
||||
ActiveReadarrBlock::EditIndexerPriorityInput,
|
||||
ActiveReadarrBlock::EditIndexerUrlInput,
|
||||
ActiveReadarrBlock::EditIndexerTagsInput,
|
||||
];
|
||||
|
||||
pub const EDIT_INDEXER_TORRENT_SELECTION_BLOCKS: &[&[ActiveReadarrBlock]] = &[
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerNameInput,
|
||||
ActiveReadarrBlock::EditIndexerUrlInput,
|
||||
],
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerToggleEnableRss,
|
||||
ActiveReadarrBlock::EditIndexerApiKeyInput,
|
||||
],
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerToggleEnableAutomaticSearch,
|
||||
ActiveReadarrBlock::EditIndexerSeedRatioInput,
|
||||
],
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerToggleEnableInteractiveSearch,
|
||||
ActiveReadarrBlock::EditIndexerTagsInput,
|
||||
],
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerPriorityInput,
|
||||
ActiveReadarrBlock::EditIndexerConfirmPrompt,
|
||||
],
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerConfirmPrompt,
|
||||
ActiveReadarrBlock::EditIndexerConfirmPrompt,
|
||||
],
|
||||
];
|
||||
|
||||
pub const EDIT_INDEXER_NZB_SELECTION_BLOCKS: &[&[ActiveReadarrBlock]] = &[
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerNameInput,
|
||||
ActiveReadarrBlock::EditIndexerUrlInput,
|
||||
],
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerToggleEnableRss,
|
||||
ActiveReadarrBlock::EditIndexerApiKeyInput,
|
||||
],
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerToggleEnableAutomaticSearch,
|
||||
ActiveReadarrBlock::EditIndexerTagsInput,
|
||||
],
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerToggleEnableInteractiveSearch,
|
||||
ActiveReadarrBlock::EditIndexerPriorityInput,
|
||||
],
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerConfirmPrompt,
|
||||
ActiveReadarrBlock::EditIndexerConfirmPrompt,
|
||||
],
|
||||
];
|
||||
|
||||
pub static INDEXER_SETTINGS_BLOCKS: [ActiveReadarrBlock; 6] = [
|
||||
ActiveReadarrBlock::AllIndexerSettingsPrompt,
|
||||
ActiveReadarrBlock::IndexerSettingsConfirmPrompt,
|
||||
ActiveReadarrBlock::IndexerSettingsMaximumSizeInput,
|
||||
ActiveReadarrBlock::IndexerSettingsMinimumAgeInput,
|
||||
ActiveReadarrBlock::IndexerSettingsRetentionInput,
|
||||
ActiveReadarrBlock::IndexerSettingsRssSyncIntervalInput,
|
||||
];
|
||||
|
||||
pub const INDEXER_SETTINGS_SELECTION_BLOCKS: &[&[ActiveReadarrBlock]] = &[
|
||||
&[ActiveReadarrBlock::IndexerSettingsMinimumAgeInput],
|
||||
&[ActiveReadarrBlock::IndexerSettingsRetentionInput],
|
||||
&[ActiveReadarrBlock::IndexerSettingsMaximumSizeInput],
|
||||
&[ActiveReadarrBlock::IndexerSettingsRssSyncIntervalInput],
|
||||
&[ActiveReadarrBlock::IndexerSettingsConfirmPrompt],
|
||||
];
|
||||
|
||||
pub static INDEXERS_BLOCKS: [ActiveReadarrBlock; 3] = [
|
||||
ActiveReadarrBlock::Indexers,
|
||||
ActiveReadarrBlock::DeleteIndexerPrompt,
|
||||
ActiveReadarrBlock::TestIndexer,
|
||||
];
|
||||
|
||||
pub static SYSTEM_DETAILS_BLOCKS: [ActiveReadarrBlock; 5] = [
|
||||
ActiveReadarrBlock::SystemLogs,
|
||||
ActiveReadarrBlock::SystemQueuedEvents,
|
||||
ActiveReadarrBlock::SystemTasks,
|
||||
ActiveReadarrBlock::SystemTaskStartConfirmPrompt,
|
||||
ActiveReadarrBlock::SystemUpdates,
|
||||
];
|
||||
|
||||
impl From<ActiveReadarrBlock> for Route {
|
||||
fn from(active_readarr_block: ActiveReadarrBlock) -> Route {
|
||||
Route::Readarr(active_readarr_block, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(ActiveReadarrBlock, Option<ActiveReadarrBlock>)> for Route {
|
||||
fn from(value: (ActiveReadarrBlock, Option<ActiveReadarrBlock>)) -> Route {
|
||||
Route::Readarr(value.0, value.1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,789 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::app::context_clues::{
|
||||
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES,
|
||||
INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
||||
};
|
||||
use crate::app::readarr::readarr_context_clues::{
|
||||
AUTHOR_DETAILS_CONTEXT_CLUES, AUTHOR_HISTORY_CONTEXT_CLUES, AUTHORS_CONTEXT_CLUES,
|
||||
MANUAL_AUTHOR_SEARCH_CONTEXT_CLUES,
|
||||
};
|
||||
use crate::models::readarr_models::{Book, ReadarrHistoryItem, ReadarrRelease};
|
||||
use crate::models::servarr_data::readarr::readarr_data::{
|
||||
ADD_AUTHOR_BLOCKS, ADD_AUTHOR_SELECTION_BLOCKS, ADD_ROOT_FOLDER_BLOCKS,
|
||||
ADD_ROOT_FOLDER_SELECTION_BLOCKS, AUTHOR_DETAILS_BLOCKS, ActiveReadarrBlock, BLOCKLIST_BLOCKS,
|
||||
BOOK_DETAILS_BLOCKS, DELETE_AUTHOR_BLOCKS, DELETE_AUTHOR_SELECTION_BLOCKS, DELETE_BOOK_BLOCKS,
|
||||
DELETE_BOOK_SELECTION_BLOCKS, DOWNLOADS_BLOCKS, EDIT_AUTHOR_BLOCKS,
|
||||
EDIT_AUTHOR_SELECTION_BLOCKS, EDIT_INDEXER_BLOCKS, EDIT_INDEXER_NZB_SELECTION_BLOCKS,
|
||||
EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, EDITION_DETAILS_BLOCKS, HISTORY_BLOCKS,
|
||||
INDEXER_SETTINGS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS, INDEXERS_BLOCKS, LIBRARY_BLOCKS,
|
||||
ROOT_FOLDERS_BLOCKS, ReadarrData, SYSTEM_DETAILS_BLOCKS,
|
||||
};
|
||||
use crate::models::{BlockSelectionState, Route};
|
||||
use bimap::BiMap;
|
||||
use chrono::{DateTime, Utc};
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use serde_json::Number;
|
||||
|
||||
#[test]
|
||||
fn test_from_active_readarr_block_to_route() {
|
||||
assert_eq!(
|
||||
Route::from(ActiveReadarrBlock::Authors),
|
||||
Route::Readarr(ActiveReadarrBlock::Authors, None)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_tuple_to_route_with_context() {
|
||||
assert_eq!(
|
||||
Route::from((
|
||||
ActiveReadarrBlock::Authors,
|
||||
Some(ActiveReadarrBlock::Authors)
|
||||
)),
|
||||
Route::Readarr(
|
||||
ActiveReadarrBlock::Authors,
|
||||
Some(ActiveReadarrBlock::Authors),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reset_delete_preferences() {
|
||||
let mut readarr_data = ReadarrData {
|
||||
delete_files: true,
|
||||
add_import_list_exclusion: true,
|
||||
..ReadarrData::default()
|
||||
};
|
||||
|
||||
readarr_data.reset_delete_preferences();
|
||||
|
||||
assert!(!readarr_data.delete_files);
|
||||
assert!(!readarr_data.add_import_list_exclusion);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reset_author_info_tabs() {
|
||||
let mut readarr_data = ReadarrData::default();
|
||||
readarr_data.books.set_items(vec![Book::default()]);
|
||||
readarr_data
|
||||
.author_releases
|
||||
.set_items(vec![ReadarrRelease::default()]);
|
||||
readarr_data
|
||||
.author_history
|
||||
.set_items(vec![ReadarrHistoryItem::default()]);
|
||||
readarr_data.author_info_tabs.index = 1;
|
||||
|
||||
readarr_data.reset_author_info_tabs();
|
||||
|
||||
assert_is_empty!(readarr_data.books);
|
||||
assert_is_empty!(readarr_data.author_releases);
|
||||
assert_is_empty!(readarr_data.author_history);
|
||||
assert_eq!(readarr_data.author_info_tabs.index, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tag_ids_to_display() {
|
||||
let mut tags_map = BiMap::new();
|
||||
tags_map.insert(3, "test 3".to_owned());
|
||||
tags_map.insert(2, "test 2".to_owned());
|
||||
tags_map.insert(1, "test 1".to_owned());
|
||||
let readarr_data = ReadarrData {
|
||||
tags_map,
|
||||
..ReadarrData::default()
|
||||
};
|
||||
|
||||
assert_str_eq!(
|
||||
readarr_data.tag_ids_to_display(&[Number::from(1), Number::from(2)]),
|
||||
"test 1, test 2"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sorted_quality_profile_names() {
|
||||
let mut quality_profile_map = BiMap::new();
|
||||
quality_profile_map.insert(3, "test 1".to_owned());
|
||||
quality_profile_map.insert(2, "test 2".to_owned());
|
||||
quality_profile_map.insert(1, "test 3".to_owned());
|
||||
let readarr_data = ReadarrData {
|
||||
quality_profile_map,
|
||||
..ReadarrData::default()
|
||||
};
|
||||
let expected_quality_profile_vec = vec![
|
||||
"test 3".to_owned(),
|
||||
"test 2".to_owned(),
|
||||
"test 1".to_owned(),
|
||||
];
|
||||
|
||||
assert_iter_eq!(
|
||||
readarr_data.sorted_quality_profile_names(),
|
||||
expected_quality_profile_vec
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sorted_metadata_profile_names() {
|
||||
let mut metadata_profile_map = BiMap::new();
|
||||
metadata_profile_map.insert(3, "test 1".to_owned());
|
||||
metadata_profile_map.insert(2, "test 2".to_owned());
|
||||
metadata_profile_map.insert(1, "test 3".to_owned());
|
||||
let readarr_data = ReadarrData {
|
||||
metadata_profile_map,
|
||||
..ReadarrData::default()
|
||||
};
|
||||
let expected_metadata_profile_vec = vec![
|
||||
"test 3".to_owned(),
|
||||
"test 2".to_owned(),
|
||||
"test 1".to_owned(),
|
||||
];
|
||||
|
||||
assert_iter_eq!(
|
||||
readarr_data.sorted_metadata_profile_names(),
|
||||
expected_metadata_profile_vec
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readarr_data_default() {
|
||||
let readarr_data = ReadarrData::default();
|
||||
|
||||
assert_none!(readarr_data.add_author_modal);
|
||||
assert_none!(readarr_data.add_author_search);
|
||||
assert!(!readarr_data.add_import_list_exclusion);
|
||||
assert_none!(readarr_data.add_root_folder_modal);
|
||||
assert_none!(readarr_data.add_searched_authors);
|
||||
assert_is_empty!(readarr_data.author_history);
|
||||
assert_is_empty!(readarr_data.author_releases);
|
||||
assert_is_empty!(readarr_data.authors);
|
||||
assert_is_empty!(readarr_data.blocklist);
|
||||
assert_none!(readarr_data.book_details_modal);
|
||||
assert_is_empty!(readarr_data.books);
|
||||
assert!(!readarr_data.delete_files);
|
||||
assert_is_empty!(readarr_data.disk_space_vec);
|
||||
assert_is_empty!(readarr_data.downloads);
|
||||
assert_none!(readarr_data.edit_author_modal);
|
||||
assert_none!(readarr_data.edit_indexer_modal);
|
||||
assert_is_empty!(readarr_data.history);
|
||||
assert_none!(readarr_data.indexer_settings);
|
||||
assert_none!(readarr_data.indexer_test_all_results);
|
||||
assert_none!(readarr_data.indexer_test_errors);
|
||||
assert_is_empty!(readarr_data.indexers);
|
||||
assert_is_empty!(readarr_data.log_details);
|
||||
assert_is_empty!(readarr_data.logs);
|
||||
assert_is_empty!(readarr_data.metadata_profile_map);
|
||||
assert!(!readarr_data.prompt_confirm);
|
||||
assert_none!(readarr_data.prompt_confirm_action);
|
||||
assert_is_empty!(readarr_data.quality_profile_map);
|
||||
assert_is_empty!(readarr_data.queued_events);
|
||||
assert_is_empty!(readarr_data.root_folders);
|
||||
assert_eq!(readarr_data.selected_block, BlockSelectionState::default());
|
||||
assert_eq!(readarr_data.start_time, <DateTime<Utc>>::default());
|
||||
assert_is_empty!(readarr_data.tags_map);
|
||||
assert_is_empty!(readarr_data.tasks);
|
||||
assert_is_empty!(readarr_data.updates);
|
||||
assert_is_empty!(readarr_data.version);
|
||||
|
||||
assert_eq!(readarr_data.main_tabs.tabs.len(), 7);
|
||||
|
||||
assert_str_eq!(readarr_data.main_tabs.tabs[0].title, "Library");
|
||||
assert_eq!(
|
||||
readarr_data.main_tabs.tabs[0].route,
|
||||
ActiveReadarrBlock::Authors.into()
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
&readarr_data.main_tabs.tabs[0].contextual_help,
|
||||
&AUTHORS_CONTEXT_CLUES
|
||||
);
|
||||
assert_none!(readarr_data.main_tabs.tabs[0].config);
|
||||
|
||||
assert_str_eq!(readarr_data.main_tabs.tabs[1].title, "Downloads");
|
||||
assert_eq!(
|
||||
readarr_data.main_tabs.tabs[1].route,
|
||||
ActiveReadarrBlock::Downloads.into()
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
&readarr_data.main_tabs.tabs[1].contextual_help,
|
||||
&DOWNLOADS_CONTEXT_CLUES
|
||||
);
|
||||
assert_none!(readarr_data.main_tabs.tabs[1].config);
|
||||
|
||||
assert_str_eq!(readarr_data.main_tabs.tabs[2].title, "Blocklist");
|
||||
assert_eq!(
|
||||
readarr_data.main_tabs.tabs[2].route,
|
||||
ActiveReadarrBlock::Blocklist.into()
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
&readarr_data.main_tabs.tabs[2].contextual_help,
|
||||
&BLOCKLIST_CONTEXT_CLUES
|
||||
);
|
||||
assert_none!(readarr_data.main_tabs.tabs[2].config);
|
||||
|
||||
assert_str_eq!(readarr_data.main_tabs.tabs[3].title, "History");
|
||||
assert_eq!(
|
||||
readarr_data.main_tabs.tabs[3].route,
|
||||
ActiveReadarrBlock::History.into()
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
&readarr_data.main_tabs.tabs[3].contextual_help,
|
||||
&HISTORY_CONTEXT_CLUES
|
||||
);
|
||||
assert_none!(readarr_data.main_tabs.tabs[3].config);
|
||||
|
||||
assert_str_eq!(readarr_data.main_tabs.tabs[4].title, "Root Folders");
|
||||
assert_eq!(
|
||||
readarr_data.main_tabs.tabs[4].route,
|
||||
ActiveReadarrBlock::RootFolders.into()
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
&readarr_data.main_tabs.tabs[4].contextual_help,
|
||||
&ROOT_FOLDERS_CONTEXT_CLUES
|
||||
);
|
||||
assert_none!(readarr_data.main_tabs.tabs[4].config);
|
||||
|
||||
assert_str_eq!(readarr_data.main_tabs.tabs[5].title, "Indexers");
|
||||
assert_eq!(
|
||||
readarr_data.main_tabs.tabs[5].route,
|
||||
ActiveReadarrBlock::Indexers.into()
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
&readarr_data.main_tabs.tabs[5].contextual_help,
|
||||
&INDEXERS_CONTEXT_CLUES
|
||||
);
|
||||
assert_none!(readarr_data.main_tabs.tabs[5].config);
|
||||
|
||||
assert_str_eq!(readarr_data.main_tabs.tabs[6].title, "System");
|
||||
assert_eq!(
|
||||
readarr_data.main_tabs.tabs[6].route,
|
||||
ActiveReadarrBlock::System.into()
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
&readarr_data.main_tabs.tabs[6].contextual_help,
|
||||
&SYSTEM_CONTEXT_CLUES
|
||||
);
|
||||
assert_none!(readarr_data.main_tabs.tabs[6].config);
|
||||
|
||||
assert_eq!(readarr_data.author_info_tabs.tabs.len(), 3);
|
||||
|
||||
assert_str_eq!(readarr_data.author_info_tabs.tabs[0].title, "Books");
|
||||
assert_eq!(
|
||||
readarr_data.author_info_tabs.tabs[0].route,
|
||||
ActiveReadarrBlock::AuthorDetails.into()
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
&readarr_data.author_info_tabs.tabs[0].contextual_help,
|
||||
&AUTHOR_DETAILS_CONTEXT_CLUES
|
||||
);
|
||||
assert_none!(readarr_data.author_info_tabs.tabs[0].config);
|
||||
|
||||
assert_str_eq!(readarr_data.author_info_tabs.tabs[1].title, "History");
|
||||
assert_eq!(
|
||||
readarr_data.author_info_tabs.tabs[1].route,
|
||||
ActiveReadarrBlock::AuthorHistory.into()
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
&readarr_data.author_info_tabs.tabs[1].contextual_help,
|
||||
&AUTHOR_HISTORY_CONTEXT_CLUES
|
||||
);
|
||||
assert_none!(readarr_data.author_info_tabs.tabs[1].config);
|
||||
|
||||
assert_str_eq!(readarr_data.author_info_tabs.tabs[2].title, "Author Search");
|
||||
assert_eq!(
|
||||
readarr_data.author_info_tabs.tabs[2].route,
|
||||
ActiveReadarrBlock::ManualAuthorSearch.into()
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
&readarr_data.author_info_tabs.tabs[2].contextual_help,
|
||||
&MANUAL_AUTHOR_SEARCH_CONTEXT_CLUES
|
||||
);
|
||||
assert_none!(readarr_data.author_info_tabs.tabs[2].config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_library_blocks_contains_expected_blocks() {
|
||||
assert_eq!(LIBRARY_BLOCKS.len(), 7);
|
||||
assert!(LIBRARY_BLOCKS.contains(&ActiveReadarrBlock::Authors));
|
||||
assert!(LIBRARY_BLOCKS.contains(&ActiveReadarrBlock::AuthorsSortPrompt));
|
||||
assert!(LIBRARY_BLOCKS.contains(&ActiveReadarrBlock::FilterAuthors));
|
||||
assert!(LIBRARY_BLOCKS.contains(&ActiveReadarrBlock::FilterAuthorsError));
|
||||
assert!(LIBRARY_BLOCKS.contains(&ActiveReadarrBlock::SearchAuthors));
|
||||
assert!(LIBRARY_BLOCKS.contains(&ActiveReadarrBlock::SearchAuthorsError));
|
||||
assert!(LIBRARY_BLOCKS.contains(&ActiveReadarrBlock::UpdateAllAuthorsPrompt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_author_details_blocks_contains_expected_blocks() {
|
||||
assert_eq!(AUTHOR_DETAILS_BLOCKS.len(), 15);
|
||||
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::AuthorDetails));
|
||||
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::AuthorHistory));
|
||||
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::AuthorHistoryDetails));
|
||||
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::AuthorHistorySortPrompt));
|
||||
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::AutomaticallySearchAuthorPrompt));
|
||||
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::FilterAuthorHistory));
|
||||
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::FilterAuthorHistoryError));
|
||||
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::ManualAuthorSearch));
|
||||
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::ManualAuthorSearchConfirmPrompt));
|
||||
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::ManualAuthorSearchSortPrompt));
|
||||
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SearchBooks));
|
||||
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SearchBooksError));
|
||||
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SearchAuthorHistory));
|
||||
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SearchAuthorHistoryError));
|
||||
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::UpdateAndScanAuthorPrompt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_book_details_blocks_contents() {
|
||||
assert_eq!(BOOK_DETAILS_BLOCKS.len(), 16);
|
||||
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::BookDetails));
|
||||
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::BookHistory));
|
||||
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::BookHistoryDetails));
|
||||
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::BookHistorySortPrompt));
|
||||
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::AutomaticallySearchBookPrompt));
|
||||
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::DeleteBookFilePrompt));
|
||||
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::FilterBookHistory));
|
||||
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::FilterBookHistoryError));
|
||||
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::BookFileInfo));
|
||||
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::ManualBookSearch));
|
||||
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::ManualBookSearchConfirmPrompt));
|
||||
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::ManualBookSearchSortPrompt));
|
||||
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SearchBookHistory));
|
||||
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SearchBookHistoryError));
|
||||
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SearchEditions));
|
||||
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SearchEditionsError));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_book_details_blocks_excludes_book_edition_details() {
|
||||
assert!(!BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::BookEditionDetails));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edition_details_blocks_contents() {
|
||||
assert_eq!(EDITION_DETAILS_BLOCKS.len(), 1);
|
||||
assert!(EDITION_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::BookEditionDetails));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blocklist_blocks_contents() {
|
||||
assert_eq!(BLOCKLIST_BLOCKS.len(), 5);
|
||||
assert!(BLOCKLIST_BLOCKS.contains(&ActiveReadarrBlock::Blocklist));
|
||||
assert!(BLOCKLIST_BLOCKS.contains(&ActiveReadarrBlock::BlocklistItemDetails));
|
||||
assert!(BLOCKLIST_BLOCKS.contains(&ActiveReadarrBlock::DeleteBlocklistItemPrompt));
|
||||
assert!(BLOCKLIST_BLOCKS.contains(&ActiveReadarrBlock::BlocklistClearAllItemsPrompt));
|
||||
assert!(BLOCKLIST_BLOCKS.contains(&ActiveReadarrBlock::BlocklistSortPrompt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_downloads_blocks_contains_expected_blocks() {
|
||||
assert_eq!(DOWNLOADS_BLOCKS.len(), 3);
|
||||
assert!(DOWNLOADS_BLOCKS.contains(&ActiveReadarrBlock::Downloads));
|
||||
assert!(DOWNLOADS_BLOCKS.contains(&ActiveReadarrBlock::DeleteDownloadPrompt));
|
||||
assert!(DOWNLOADS_BLOCKS.contains(&ActiveReadarrBlock::UpdateDownloadsPrompt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_blocks_contains_expected_blocks() {
|
||||
assert_eq!(HISTORY_BLOCKS.len(), 7);
|
||||
assert!(HISTORY_BLOCKS.contains(&ActiveReadarrBlock::History));
|
||||
assert!(HISTORY_BLOCKS.contains(&ActiveReadarrBlock::HistoryItemDetails));
|
||||
assert!(HISTORY_BLOCKS.contains(&ActiveReadarrBlock::HistorySortPrompt));
|
||||
assert!(HISTORY_BLOCKS.contains(&ActiveReadarrBlock::SearchHistory));
|
||||
assert!(HISTORY_BLOCKS.contains(&ActiveReadarrBlock::SearchHistoryError));
|
||||
assert!(HISTORY_BLOCKS.contains(&ActiveReadarrBlock::FilterHistory));
|
||||
assert!(HISTORY_BLOCKS.contains(&ActiveReadarrBlock::FilterHistoryError));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_author_blocks_contents() {
|
||||
assert_eq!(ADD_AUTHOR_BLOCKS.len(), 12);
|
||||
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorAlreadyInLibrary));
|
||||
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorConfirmPrompt));
|
||||
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorEmptySearchResults));
|
||||
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorPrompt));
|
||||
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorSearchInput));
|
||||
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorSearchResults));
|
||||
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorSelectMetadataProfile));
|
||||
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorSelectMonitor));
|
||||
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorSelectMonitorNewItems));
|
||||
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorSelectQualityProfile));
|
||||
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorSelectRootFolder));
|
||||
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorTagsInput));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_author_selection_blocks_ordering() {
|
||||
let mut add_author_block_iter = ADD_AUTHOR_SELECTION_BLOCKS.iter();
|
||||
|
||||
assert_eq!(
|
||||
add_author_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::AddAuthorSelectRootFolder]
|
||||
);
|
||||
assert_eq!(
|
||||
add_author_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::AddAuthorSelectMonitor]
|
||||
);
|
||||
assert_eq!(
|
||||
add_author_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::AddAuthorSelectMonitorNewItems]
|
||||
);
|
||||
assert_eq!(
|
||||
add_author_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::AddAuthorSelectQualityProfile]
|
||||
);
|
||||
assert_eq!(
|
||||
add_author_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::AddAuthorSelectMetadataProfile]
|
||||
);
|
||||
assert_eq!(
|
||||
add_author_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::AddAuthorTagsInput]
|
||||
);
|
||||
assert_eq!(
|
||||
add_author_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::AddAuthorConfirmPrompt]
|
||||
);
|
||||
assert_none!(add_author_block_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_author_blocks_contents() {
|
||||
assert_eq!(DELETE_AUTHOR_BLOCKS.len(), 4);
|
||||
assert!(DELETE_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::DeleteAuthorPrompt));
|
||||
assert!(DELETE_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::DeleteAuthorConfirmPrompt));
|
||||
assert!(DELETE_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::DeleteAuthorToggleDeleteFile));
|
||||
assert!(DELETE_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::DeleteAuthorToggleAddListExclusion));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_author_selection_blocks_ordering() {
|
||||
let mut delete_author_block_iter = DELETE_AUTHOR_SELECTION_BLOCKS.iter();
|
||||
|
||||
assert_eq!(
|
||||
delete_author_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::DeleteAuthorToggleDeleteFile]
|
||||
);
|
||||
assert_eq!(
|
||||
delete_author_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::DeleteAuthorToggleAddListExclusion]
|
||||
);
|
||||
assert_eq!(
|
||||
delete_author_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::DeleteAuthorConfirmPrompt]
|
||||
);
|
||||
assert_none!(delete_author_block_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_book_blocks_contents() {
|
||||
assert_eq!(DELETE_BOOK_BLOCKS.len(), 4);
|
||||
assert!(DELETE_BOOK_BLOCKS.contains(&ActiveReadarrBlock::DeleteBookPrompt));
|
||||
assert!(DELETE_BOOK_BLOCKS.contains(&ActiveReadarrBlock::DeleteBookConfirmPrompt));
|
||||
assert!(DELETE_BOOK_BLOCKS.contains(&ActiveReadarrBlock::DeleteBookToggleDeleteFile));
|
||||
assert!(DELETE_BOOK_BLOCKS.contains(&ActiveReadarrBlock::DeleteBookToggleAddListExclusion));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_book_selection_blocks_ordering() {
|
||||
let mut delete_book_block_iter = DELETE_BOOK_SELECTION_BLOCKS.iter();
|
||||
|
||||
assert_eq!(
|
||||
delete_book_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::DeleteBookToggleDeleteFile]
|
||||
);
|
||||
assert_eq!(
|
||||
delete_book_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::DeleteBookToggleAddListExclusion]
|
||||
);
|
||||
assert_eq!(
|
||||
delete_book_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::DeleteBookConfirmPrompt]
|
||||
);
|
||||
assert_none!(delete_book_block_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_author_blocks() {
|
||||
assert_eq!(EDIT_AUTHOR_BLOCKS.len(), 8);
|
||||
assert!(EDIT_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::EditAuthorPrompt));
|
||||
assert!(EDIT_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::EditAuthorConfirmPrompt));
|
||||
assert!(EDIT_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::EditAuthorPathInput));
|
||||
assert!(EDIT_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::EditAuthorSelectMetadataProfile));
|
||||
assert!(EDIT_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::EditAuthorSelectMonitorNewItems));
|
||||
assert!(EDIT_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::EditAuthorSelectQualityProfile));
|
||||
assert!(EDIT_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::EditAuthorTagsInput));
|
||||
assert!(EDIT_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::EditAuthorToggleMonitored));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_author_selection_blocks_ordering() {
|
||||
let mut edit_author_block_iter = EDIT_AUTHOR_SELECTION_BLOCKS.iter();
|
||||
|
||||
assert_eq!(
|
||||
edit_author_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::EditAuthorToggleMonitored]
|
||||
);
|
||||
assert_eq!(
|
||||
edit_author_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::EditAuthorSelectMonitorNewItems]
|
||||
);
|
||||
assert_eq!(
|
||||
edit_author_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::EditAuthorSelectQualityProfile]
|
||||
);
|
||||
assert_eq!(
|
||||
edit_author_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::EditAuthorSelectMetadataProfile]
|
||||
);
|
||||
assert_eq!(
|
||||
edit_author_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::EditAuthorPathInput]
|
||||
);
|
||||
assert_eq!(
|
||||
edit_author_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::EditAuthorTagsInput]
|
||||
);
|
||||
assert_eq!(
|
||||
edit_author_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::EditAuthorConfirmPrompt]
|
||||
);
|
||||
assert_none!(edit_author_block_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_root_folders_blocks_contents() {
|
||||
assert_eq!(ROOT_FOLDERS_BLOCKS.len(), 2);
|
||||
assert!(ROOT_FOLDERS_BLOCKS.contains(&ActiveReadarrBlock::RootFolders));
|
||||
assert!(ROOT_FOLDERS_BLOCKS.contains(&ActiveReadarrBlock::DeleteRootFolderPrompt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_root_folder_blocks_contents() {
|
||||
assert_eq!(ADD_ROOT_FOLDER_BLOCKS.len(), 9);
|
||||
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderPrompt));
|
||||
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderConfirmPrompt));
|
||||
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderNameInput));
|
||||
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderPathInput));
|
||||
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderSelectMonitor));
|
||||
assert!(
|
||||
ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderSelectMonitorNewItems)
|
||||
);
|
||||
assert!(
|
||||
ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderSelectQualityProfile)
|
||||
);
|
||||
assert!(
|
||||
ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderSelectMetadataProfile)
|
||||
);
|
||||
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderTagsInput));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_root_folder_selection_blocks_ordering() {
|
||||
let mut add_root_folder_block_iter = ADD_ROOT_FOLDER_SELECTION_BLOCKS.iter();
|
||||
|
||||
assert_eq!(
|
||||
add_root_folder_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::AddRootFolderNameInput]
|
||||
);
|
||||
assert_eq!(
|
||||
add_root_folder_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::AddRootFolderPathInput]
|
||||
);
|
||||
assert_eq!(
|
||||
add_root_folder_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::AddRootFolderSelectMonitor]
|
||||
);
|
||||
assert_eq!(
|
||||
add_root_folder_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::AddRootFolderSelectMonitorNewItems]
|
||||
);
|
||||
assert_eq!(
|
||||
add_root_folder_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::AddRootFolderSelectQualityProfile]
|
||||
);
|
||||
assert_eq!(
|
||||
add_root_folder_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::AddRootFolderSelectMetadataProfile]
|
||||
);
|
||||
assert_eq!(
|
||||
add_root_folder_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::AddRootFolderTagsInput]
|
||||
);
|
||||
assert_eq!(
|
||||
add_root_folder_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::AddRootFolderConfirmPrompt]
|
||||
);
|
||||
assert_none!(add_root_folder_block_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_blocks_contents() {
|
||||
assert_eq!(EDIT_INDEXER_BLOCKS.len(), 11);
|
||||
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerPrompt));
|
||||
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerConfirmPrompt));
|
||||
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerApiKeyInput));
|
||||
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerNameInput));
|
||||
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerSeedRatioInput));
|
||||
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerToggleEnableRss));
|
||||
assert!(
|
||||
EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerToggleEnableAutomaticSearch)
|
||||
);
|
||||
assert!(
|
||||
EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerToggleEnableInteractiveSearch)
|
||||
);
|
||||
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerPriorityInput));
|
||||
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerUrlInput));
|
||||
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerTagsInput));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_torrent_selection_blocks_ordering() {
|
||||
let mut edit_indexer_block_iter = EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.iter();
|
||||
|
||||
assert_eq!(
|
||||
edit_indexer_block_iter.next().unwrap(),
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerNameInput,
|
||||
ActiveReadarrBlock::EditIndexerUrlInput
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_block_iter.next().unwrap(),
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerToggleEnableRss,
|
||||
ActiveReadarrBlock::EditIndexerApiKeyInput
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_block_iter.next().unwrap(),
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerToggleEnableAutomaticSearch,
|
||||
ActiveReadarrBlock::EditIndexerSeedRatioInput
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_block_iter.next().unwrap(),
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerToggleEnableInteractiveSearch,
|
||||
ActiveReadarrBlock::EditIndexerTagsInput
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_block_iter.next().unwrap(),
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerPriorityInput,
|
||||
ActiveReadarrBlock::EditIndexerConfirmPrompt
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_block_iter.next().unwrap(),
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerConfirmPrompt,
|
||||
ActiveReadarrBlock::EditIndexerConfirmPrompt
|
||||
]
|
||||
);
|
||||
assert_none!(edit_indexer_block_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_nzb_selection_blocks_ordering() {
|
||||
let mut edit_indexer_block_iter = EDIT_INDEXER_NZB_SELECTION_BLOCKS.iter();
|
||||
|
||||
assert_eq!(
|
||||
edit_indexer_block_iter.next().unwrap(),
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerNameInput,
|
||||
ActiveReadarrBlock::EditIndexerUrlInput
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_block_iter.next().unwrap(),
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerToggleEnableRss,
|
||||
ActiveReadarrBlock::EditIndexerApiKeyInput
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_block_iter.next().unwrap(),
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerToggleEnableAutomaticSearch,
|
||||
ActiveReadarrBlock::EditIndexerTagsInput
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_block_iter.next().unwrap(),
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerToggleEnableInteractiveSearch,
|
||||
ActiveReadarrBlock::EditIndexerPriorityInput
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_block_iter.next().unwrap(),
|
||||
&[
|
||||
ActiveReadarrBlock::EditIndexerConfirmPrompt,
|
||||
ActiveReadarrBlock::EditIndexerConfirmPrompt
|
||||
]
|
||||
);
|
||||
assert_none!(edit_indexer_block_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_indexer_settings_blocks_contents() {
|
||||
assert_eq!(INDEXER_SETTINGS_BLOCKS.len(), 6);
|
||||
assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveReadarrBlock::AllIndexerSettingsPrompt));
|
||||
assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveReadarrBlock::IndexerSettingsConfirmPrompt));
|
||||
assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveReadarrBlock::IndexerSettingsMaximumSizeInput));
|
||||
assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveReadarrBlock::IndexerSettingsMinimumAgeInput));
|
||||
assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveReadarrBlock::IndexerSettingsRetentionInput));
|
||||
assert!(
|
||||
INDEXER_SETTINGS_BLOCKS.contains(&ActiveReadarrBlock::IndexerSettingsRssSyncIntervalInput)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_indexer_settings_selection_blocks_ordering() {
|
||||
let mut indexer_settings_block_iter = INDEXER_SETTINGS_SELECTION_BLOCKS.iter();
|
||||
|
||||
assert_eq!(
|
||||
indexer_settings_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::IndexerSettingsMinimumAgeInput]
|
||||
);
|
||||
assert_eq!(
|
||||
indexer_settings_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::IndexerSettingsRetentionInput]
|
||||
);
|
||||
assert_eq!(
|
||||
indexer_settings_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::IndexerSettingsMaximumSizeInput]
|
||||
);
|
||||
assert_eq!(
|
||||
indexer_settings_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::IndexerSettingsRssSyncIntervalInput]
|
||||
);
|
||||
assert_eq!(
|
||||
indexer_settings_block_iter.next().unwrap(),
|
||||
&[ActiveReadarrBlock::IndexerSettingsConfirmPrompt]
|
||||
);
|
||||
assert_none!(indexer_settings_block_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_indexers_blocks_contents() {
|
||||
assert_eq!(INDEXERS_BLOCKS.len(), 3);
|
||||
assert!(INDEXERS_BLOCKS.contains(&ActiveReadarrBlock::Indexers));
|
||||
assert!(INDEXERS_BLOCKS.contains(&ActiveReadarrBlock::DeleteIndexerPrompt));
|
||||
assert!(INDEXERS_BLOCKS.contains(&ActiveReadarrBlock::TestIndexer));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_indexers_blocks_excludes_test_all_indexers() {
|
||||
assert!(!INDEXERS_BLOCKS.contains(&ActiveReadarrBlock::TestAllIndexers));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_details_blocks_contents() {
|
||||
assert_eq!(SYSTEM_DETAILS_BLOCKS.len(), 5);
|
||||
assert!(SYSTEM_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SystemLogs));
|
||||
assert!(SYSTEM_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SystemQueuedEvents));
|
||||
assert!(SYSTEM_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SystemTasks));
|
||||
assert!(SYSTEM_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SystemTaskStartConfirmPrompt));
|
||||
assert!(SYSTEM_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SystemUpdates));
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,14 @@ use tokio_util::sync::CancellationToken;
|
||||
use crate::app::{App, ServarrConfig};
|
||||
use crate::models::Serdeable;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::network::readarr_network::ReadarrEvent;
|
||||
#[cfg(test)]
|
||||
use mockall::automock;
|
||||
use reqwest::header::HeaderMap;
|
||||
|
||||
pub mod lidarr_network;
|
||||
pub mod radarr_network;
|
||||
pub mod readarr_network;
|
||||
pub mod sonarr_network;
|
||||
mod utils;
|
||||
|
||||
@@ -50,6 +52,7 @@ pub enum NetworkEvent {
|
||||
Radarr(RadarrEvent),
|
||||
Sonarr(SonarrEvent),
|
||||
Lidarr(LidarrEvent),
|
||||
Readarr(ReadarrEvent),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -75,6 +78,10 @@ impl NetworkTrait for Network<'_, '_> {
|
||||
.handle_lidarr_event(lidarr_event)
|
||||
.await
|
||||
.map(Serdeable::from),
|
||||
NetworkEvent::Readarr(readarr_event) => self
|
||||
.handle_readarr_event(readarr_event)
|
||||
.await
|
||||
.map(Serdeable::from),
|
||||
};
|
||||
|
||||
let mut app = self.app.lock().await;
|
||||
@@ -242,6 +249,7 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
NetworkEvent::Radarr(_) => (7878, "v3"),
|
||||
NetworkEvent::Sonarr(_) => (8989, "v3"),
|
||||
NetworkEvent::Lidarr(_) => (8686, "v1"),
|
||||
NetworkEvent::Readarr(_) => (8787, "v1"),
|
||||
};
|
||||
let mut uri = if let Some(servarr_uri) = uri {
|
||||
format!("{servarr_uri}/api/{api_version}{resource}")
|
||||
|
||||
@@ -944,6 +944,7 @@ pub(in crate::network) mod test_utils {
|
||||
let network_event_clone: NetworkEvent = network_event.clone().into();
|
||||
let api_version = match &network_event_clone {
|
||||
NetworkEvent::Lidarr(_) => "v1",
|
||||
NetworkEvent::Readarr(_) => "v1",
|
||||
_ => "v3",
|
||||
};
|
||||
let mut server = Server::new_async().await;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use super::{NetworkEvent, NetworkResource};
|
||||
use crate::models::readarr_models::ReadarrSerdeable;
|
||||
use crate::network::Network;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub enum ReadarrEvent {}
|
||||
|
||||
impl NetworkResource for ReadarrEvent {
|
||||
fn resource(&self) -> &'static str {
|
||||
match *self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ReadarrEvent> for NetworkEvent {
|
||||
fn from(readarr_event: ReadarrEvent) -> Self {
|
||||
NetworkEvent::Readarr(readarr_event)
|
||||
}
|
||||
}
|
||||
|
||||
impl Network<'_, '_> {
|
||||
pub async fn handle_readarr_event(
|
||||
&mut self,
|
||||
readarr_event: ReadarrEvent,
|
||||
) -> Result<ReadarrSerdeable> {
|
||||
match readarr_event {}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use ratatui::widgets::Paragraph;
|
||||
use ratatui::widgets::Tabs;
|
||||
use ratatui::widgets::Wrap;
|
||||
use ratatui::widgets::{Clear, Row};
|
||||
use readarr_ui::ReadarrUi;
|
||||
use sonarr_ui::SonarrUi;
|
||||
use utils::layout_block;
|
||||
|
||||
@@ -32,6 +33,7 @@ use crate::ui::widgets::popup::{Popup, Size};
|
||||
mod builtin_themes;
|
||||
mod lidarr_ui;
|
||||
mod radarr_ui;
|
||||
mod readarr_ui;
|
||||
mod sonarr_ui;
|
||||
mod styles;
|
||||
pub mod theme;
|
||||
@@ -94,6 +96,10 @@ pub fn ui(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
LidarrUi::draw_context_row(f, app, context_area);
|
||||
LidarrUi::draw(f, app, table_area);
|
||||
}
|
||||
route if ReadarrUi::accepts(route) => {
|
||||
ReadarrUi::draw_context_row(f, app, context_area);
|
||||
ReadarrUi::draw(f, app, table_area);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::models::readarr_models::{Author, AuthorStatistics, AuthorStatus};
|
||||
use crate::models::servarr_data::readarr::readarr_data::{ActiveReadarrBlock, LIBRARY_BLOCKS};
|
||||
use crate::ui::DrawUi;
|
||||
use crate::ui::readarr_ui::library::{LibraryUi, decorate_author_row_with_style};
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
use pretty_assertions::assert_eq;
|
||||
use ratatui::widgets::{Cell, Row};
|
||||
|
||||
#[test]
|
||||
fn test_library_ui_accepts() {
|
||||
for active_readarr_block in ActiveReadarrBlock::iter() {
|
||||
if LIBRARY_BLOCKS.contains(&active_readarr_block) {
|
||||
assert!(
|
||||
LibraryUi::accepts(active_readarr_block.into()),
|
||||
"{active_readarr_block} is not accepted by the LibraryUi"
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
!LibraryUi::accepts(active_readarr_block.into()),
|
||||
"{active_readarr_block} should not be accepted by LibraryUi"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_row_with_style_unmonitored() {
|
||||
let author = Author::default();
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_author_row_with_style(&author, row.clone());
|
||||
|
||||
assert_eq!(style, row.unmonitored());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_row_with_style_downloaded_when_ended_and_all_books_present() {
|
||||
let author = Author {
|
||||
monitored: true,
|
||||
status: AuthorStatus::Ended,
|
||||
statistics: Some(AuthorStatistics {
|
||||
book_file_count: 10,
|
||||
total_book_count: 10,
|
||||
..AuthorStatistics::default()
|
||||
}),
|
||||
..Author::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_author_row_with_style(&author, row.clone());
|
||||
|
||||
assert_eq!(style, row.downloaded());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_row_with_style_missing_when_ended_and_books_are_missing() {
|
||||
let author = Author {
|
||||
monitored: true,
|
||||
status: AuthorStatus::Ended,
|
||||
statistics: Some(AuthorStatistics {
|
||||
book_file_count: 5,
|
||||
total_book_count: 10,
|
||||
..AuthorStatistics::default()
|
||||
}),
|
||||
..Author::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_author_row_with_style(&author, row.clone());
|
||||
|
||||
assert_eq!(style, row.missing());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_row_with_style_indeterminate_when_ended_and_no_statistics() {
|
||||
let author = Author {
|
||||
monitored: true,
|
||||
status: AuthorStatus::Ended,
|
||||
statistics: None,
|
||||
..Author::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_author_row_with_style(&author, row.clone());
|
||||
|
||||
assert_eq!(style, row.indeterminate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_row_with_style_indeterminate_when_ended_and_total_book_count_is_zero() {
|
||||
let author = Author {
|
||||
monitored: true,
|
||||
status: AuthorStatus::Ended,
|
||||
statistics: Some(AuthorStatistics {
|
||||
book_file_count: 0,
|
||||
total_book_count: 0,
|
||||
..AuthorStatistics::default()
|
||||
}),
|
||||
..Author::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_author_row_with_style(&author, row.clone());
|
||||
|
||||
assert_eq!(style, row.missing());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_row_with_style_unreleased_when_continuing_and_all_books_present() {
|
||||
let author = Author {
|
||||
monitored: true,
|
||||
status: AuthorStatus::Continuing,
|
||||
statistics: Some(AuthorStatistics {
|
||||
book_file_count: 10,
|
||||
total_book_count: 10,
|
||||
..AuthorStatistics::default()
|
||||
}),
|
||||
..Author::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_author_row_with_style(&author, row.clone());
|
||||
|
||||
assert_eq!(style, row.unreleased());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_row_with_style_missing_when_continuing_and_books_are_missing() {
|
||||
let author = Author {
|
||||
monitored: true,
|
||||
status: AuthorStatus::Continuing,
|
||||
statistics: Some(AuthorStatistics {
|
||||
book_file_count: 5,
|
||||
total_book_count: 10,
|
||||
..AuthorStatistics::default()
|
||||
}),
|
||||
..Author::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_author_row_with_style(&author, row.clone());
|
||||
|
||||
assert_eq!(style, row.missing());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_row_with_style_indeterminate_when_continuing_and_no_statistics() {
|
||||
let author = Author {
|
||||
monitored: true,
|
||||
status: AuthorStatus::Continuing,
|
||||
statistics: None,
|
||||
..Author::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_author_row_with_style(&author, row.clone());
|
||||
|
||||
assert_eq!(style, row.indeterminate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_row_with_style_defaults_to_indeterminate_for_deleted_status() {
|
||||
let author = Author {
|
||||
monitored: true,
|
||||
status: AuthorStatus::Deleted,
|
||||
..Author::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_author_row_with_style(&author, row.clone());
|
||||
|
||||
assert_eq!(style, row.indeterminate());
|
||||
}
|
||||
|
||||
mod snapshot_tests {
|
||||
use crate::app::App;
|
||||
use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock;
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::ui::DrawUi;
|
||||
use crate::ui::readarr_ui::library::LibraryUi;
|
||||
use crate::ui::ui_test_utils::test_utils::{TerminalSize, render_to_string_with_app};
|
||||
|
||||
#[rstest]
|
||||
fn test_library_ui_renders(
|
||||
#[values(
|
||||
ActiveReadarrBlock::Authors,
|
||||
ActiveReadarrBlock::AuthorsSortPrompt,
|
||||
ActiveReadarrBlock::SearchAuthors,
|
||||
ActiveReadarrBlock::SearchAuthorsError,
|
||||
ActiveReadarrBlock::FilterAuthors,
|
||||
ActiveReadarrBlock::FilterAuthorsError
|
||||
)]
|
||||
active_readarr_block: ActiveReadarrBlock,
|
||||
) {
|
||||
let mut app = App::test_default_fully_populated();
|
||||
app.push_navigation_stack(active_readarr_block.into());
|
||||
|
||||
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||
LibraryUi::draw(f, app, f.area());
|
||||
});
|
||||
|
||||
insta::assert_snapshot!(format!("readarr_library_{active_readarr_block}"), output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_library_ui_renders_loading() {
|
||||
let mut app = App::test_default_fully_populated();
|
||||
app.is_loading = true;
|
||||
app.push_navigation_stack(ActiveReadarrBlock::Authors.into());
|
||||
|
||||
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||
LibraryUi::draw(f, app, f.area());
|
||||
});
|
||||
|
||||
insta::assert_snapshot!(output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_library_ui_renders_empty() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveReadarrBlock::Authors.into());
|
||||
|
||||
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||
LibraryUi::draw(f, app, f.area());
|
||||
});
|
||||
|
||||
insta::assert_snapshot!(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
use ratatui::{
|
||||
Frame,
|
||||
layout::{Constraint, Rect},
|
||||
widgets::{Cell, Row},
|
||||
};
|
||||
|
||||
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||
use crate::utils::convert_to_gb;
|
||||
use crate::{
|
||||
app::App,
|
||||
models::{
|
||||
Route,
|
||||
readarr_models::{Author, AuthorStatus},
|
||||
servarr_data::readarr::readarr_data::{ActiveReadarrBlock, LIBRARY_BLOCKS},
|
||||
},
|
||||
ui::{
|
||||
DrawUi,
|
||||
styles::ManagarrStyle,
|
||||
utils::{get_width_from_percentage, layout_block_top_border},
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "library_ui_tests.rs"]
|
||||
mod library_ui_tests;
|
||||
|
||||
pub(super) struct LibraryUi;
|
||||
|
||||
impl DrawUi for LibraryUi {
|
||||
fn accepts(route: Route) -> bool {
|
||||
if let Route::Readarr(active_readarr_block, _) = route {
|
||||
return LIBRARY_BLOCKS.contains(&active_readarr_block);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
draw_library(f, app, area);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
if let Route::Readarr(active_readarr_block, _) = app.get_current_route() {
|
||||
let current_selection = if !app.data.readarr_data.authors.items.is_empty() {
|
||||
app.data.readarr_data.authors.current_selection().clone()
|
||||
} else {
|
||||
Author::default()
|
||||
};
|
||||
let quality_profile_map = &app.data.readarr_data.quality_profile_map;
|
||||
let metadata_profile_map = &app.data.readarr_data.metadata_profile_map;
|
||||
let tags_map = &app.data.readarr_data.tags_map;
|
||||
let content = Some(&mut app.data.readarr_data.authors);
|
||||
|
||||
let authors_table_row_mapping = |author: &Author| {
|
||||
author.author_name.scroll_left_or_reset(
|
||||
get_width_from_percentage(area, 25),
|
||||
*author == current_selection,
|
||||
app.should_text_scroll,
|
||||
);
|
||||
let monitored = if author.monitored { "🏷" } else { "" };
|
||||
let author_type = author.author_type.clone().unwrap_or_default();
|
||||
let size = author
|
||||
.statistics
|
||||
.as_ref()
|
||||
.map_or(0f64, |stats| convert_to_gb(stats.size_on_disk));
|
||||
let quality_profile = quality_profile_map
|
||||
.get_by_left(&author.quality_profile_id)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let metadata_profile = metadata_profile_map
|
||||
.get_by_left(&author.metadata_profile_id)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let books = author
|
||||
.statistics
|
||||
.as_ref()
|
||||
.map_or(0, |stats| stats.book_count);
|
||||
let tags = author
|
||||
.tags
|
||||
.iter()
|
||||
.filter_map(|tag_id| {
|
||||
let id = tag_id.as_i64()?;
|
||||
tags_map.get_by_left(&id).cloned()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
decorate_author_row_with_style(
|
||||
author,
|
||||
Row::new(vec![
|
||||
Cell::from(author.author_name.to_string()),
|
||||
Cell::from(author_type),
|
||||
Cell::from(author.status.to_display_str()),
|
||||
Cell::from(quality_profile),
|
||||
Cell::from(metadata_profile),
|
||||
Cell::from(books.to_string()),
|
||||
Cell::from(format!("{size:.2} GB")),
|
||||
Cell::from(monitored.to_owned()),
|
||||
Cell::from(tags),
|
||||
]),
|
||||
)
|
||||
};
|
||||
let authors_table = ManagarrTable::new(content, authors_table_row_mapping)
|
||||
.block(layout_block_top_border())
|
||||
.loading(app.is_loading)
|
||||
.sorting(active_readarr_block == ActiveReadarrBlock::AuthorsSortPrompt)
|
||||
.searching(active_readarr_block == ActiveReadarrBlock::SearchAuthors)
|
||||
.filtering(active_readarr_block == ActiveReadarrBlock::FilterAuthors)
|
||||
.search_produced_empty_results(active_readarr_block == ActiveReadarrBlock::SearchAuthorsError)
|
||||
.filter_produced_empty_results(active_readarr_block == ActiveReadarrBlock::FilterAuthorsError)
|
||||
.headers([
|
||||
"Name",
|
||||
"Type",
|
||||
"Status",
|
||||
"Quality Profile",
|
||||
"Metadata Profile",
|
||||
"Books",
|
||||
"Size",
|
||||
"Monitored",
|
||||
"Tags",
|
||||
])
|
||||
.constraints([
|
||||
Constraint::Percentage(25),
|
||||
Constraint::Percentage(9),
|
||||
Constraint::Percentage(8),
|
||||
Constraint::Percentage(13),
|
||||
Constraint::Percentage(13),
|
||||
Constraint::Percentage(7),
|
||||
Constraint::Percentage(8),
|
||||
Constraint::Percentage(6),
|
||||
Constraint::Percentage(11),
|
||||
]);
|
||||
|
||||
if [
|
||||
ActiveReadarrBlock::SearchAuthors,
|
||||
ActiveReadarrBlock::FilterAuthors,
|
||||
]
|
||||
.contains(&active_readarr_block)
|
||||
{
|
||||
authors_table.show_cursor(f, area);
|
||||
}
|
||||
|
||||
f.render_widget(authors_table, area);
|
||||
}
|
||||
}
|
||||
|
||||
fn decorate_author_row_with_style<'a>(author: &Author, row: Row<'a>) -> Row<'a> {
|
||||
if !author.monitored {
|
||||
return row.unmonitored();
|
||||
}
|
||||
|
||||
match author.status {
|
||||
AuthorStatus::Ended => {
|
||||
if let Some(ref stats) = author.statistics {
|
||||
return if stats.book_file_count == stats.total_book_count && stats.total_book_count > 0 {
|
||||
row.downloaded()
|
||||
} else {
|
||||
row.missing()
|
||||
};
|
||||
}
|
||||
row.indeterminate()
|
||||
}
|
||||
AuthorStatus::Continuing => {
|
||||
if let Some(ref stats) = author.statistics {
|
||||
return if stats.book_file_count == stats.total_book_count && stats.total_book_count > 0 {
|
||||
row.unreleased()
|
||||
} else {
|
||||
row.missing()
|
||||
};
|
||||
}
|
||||
row.indeterminate()
|
||||
}
|
||||
_ => row.indeterminate(),
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: src/ui/readarr_ui/library/library_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
---
|
||||
source: src/ui/readarr_ui/library/library_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
Loading ...
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
---
|
||||
source: src/ui/readarr_ui/library/library_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
Name ▼ Type Status Quality Profile Metadata Profile Books Size Monitored Tags
|
||||
=> Test Author Continuing Standard Standard 0 0.00 GB 🏷 alex
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
---
|
||||
source: src/ui/readarr_ui/library/library_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
Name Type Status Quality Profile Metadata Profile Books Size Monitored Tags
|
||||
=> Test Author Continuing Standard Standard 0 0.00 GB 🏷 alex
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
╭───────────────────────────────╮
|
||||
│Name │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
╰───────────────────────────────╯
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
---
|
||||
source: src/ui/readarr_ui/library/library_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
Name ▼ Type Status Quality Profile Metadata Profile Books Size Monitored Tags
|
||||
=> Test Author Continuing Standard Standard 0 0.00 GB 🏷 alex
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
╭───────────────── Filter ──────────────────╮
|
||||
│author filter │
|
||||
╰─────────────────────────────────────────────╯
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
---
|
||||
source: src/ui/readarr_ui/library/library_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
Name ▼ Type Status Quality Profile Metadata Profile Books Size Monitored Tags
|
||||
=> Test Author Continuing Standard Standard 0 0.00 GB 🏷 alex
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
╭─────────────── Error ───────────────╮
|
||||
│The given filter produced empty results│
|
||||
│ │
|
||||
╰───────────────────────────────────────╯
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
---
|
||||
source: src/ui/readarr_ui/library/library_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
Name ▼ Type Status Quality Profile Metadata Profile Books Size Monitored Tags
|
||||
=> Test Author Continuing Standard Standard 0 0.00 GB 🏷 alex
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
╭───────────────── Search ──────────────────╮
|
||||
│author search │
|
||||
╰─────────────────────────────────────────────╯
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
---
|
||||
source: src/ui/readarr_ui/library/library_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
Name ▼ Type Status Quality Profile Metadata Profile Books Size Monitored Tags
|
||||
=> Test Author Continuing Standard Standard 0 0.00 GB 🏷 alex
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
╭─────────────── Error ───────────────╮
|
||||
│ No items found matching search │
|
||||
│ │
|
||||
╰───────────────────────────────────────╯
|
||||
@@ -0,0 +1,217 @@
|
||||
#[cfg(test)]
|
||||
use crate::ui::ui_test_utils::test_utils::Utc;
|
||||
use chrono::Duration;
|
||||
#[cfg(not(test))]
|
||||
use chrono::Utc;
|
||||
use library::LibraryUi;
|
||||
use ratatui::{
|
||||
Frame,
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::Stylize,
|
||||
text::Text,
|
||||
widgets::Paragraph,
|
||||
};
|
||||
use std::{cmp, iter};
|
||||
|
||||
use super::{
|
||||
DrawUi, draw_tabs,
|
||||
styles::ManagarrStyle,
|
||||
utils::{
|
||||
borderless_block, layout_block, line_gauge_with_label, line_gauge_with_title, title_block,
|
||||
},
|
||||
widgets::loading_block::LoadingBlock,
|
||||
};
|
||||
use crate::ui::utils::{extract_monitored_disk_space_vec, extract_monitored_root_folders};
|
||||
use crate::{
|
||||
app::App,
|
||||
logos::READARR_LOGO,
|
||||
models::{
|
||||
Route,
|
||||
readarr_models::DownloadRecord,
|
||||
servarr_data::readarr::readarr_data::ReadarrData,
|
||||
servarr_models::{DiskSpace, RootFolder},
|
||||
},
|
||||
utils::convert_to_gb,
|
||||
};
|
||||
|
||||
mod library;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "readarr_ui_tests.rs"]
|
||||
mod readarr_ui_tests;
|
||||
|
||||
pub(super) struct ReadarrUi;
|
||||
|
||||
impl DrawUi for ReadarrUi {
|
||||
fn accepts(route: Route) -> bool {
|
||||
matches!(route, Route::Readarr(_, _))
|
||||
}
|
||||
|
||||
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
let content_area = draw_tabs(f, area, "Authors", &app.data.readarr_data.main_tabs);
|
||||
let route = app.get_current_route();
|
||||
|
||||
match route {
|
||||
_ if LibraryUi::accepts(route) => LibraryUi::draw(f, app, content_area),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_context_row(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
|
||||
let [main_area, logo_area] =
|
||||
Layout::horizontal([Constraint::Fill(0), Constraint::Length(20)]).areas(area);
|
||||
|
||||
let [stats_area, downloads_area] =
|
||||
Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]).areas(main_area);
|
||||
|
||||
draw_stats_context(f, app, stats_area);
|
||||
draw_downloads_context(f, app, downloads_area);
|
||||
draw_readarr_logo(f, logo_area);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
|
||||
let block = title_block("Stats");
|
||||
|
||||
if !app.data.readarr_data.version.is_empty() {
|
||||
f.render_widget(block, area);
|
||||
let ReadarrData {
|
||||
root_folders,
|
||||
disk_space_vec,
|
||||
start_time,
|
||||
..
|
||||
} = &app.data.readarr_data;
|
||||
let monitored_disk_space_vec = extract_monitored_disk_space_vec(app, disk_space_vec.clone());
|
||||
let monitored_root_folders = extract_monitored_root_folders(app, root_folders.items.clone());
|
||||
|
||||
let mut constraints = vec![
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
];
|
||||
|
||||
constraints.append(
|
||||
&mut iter::repeat_n(
|
||||
Constraint::Length(1),
|
||||
monitored_disk_space_vec.len() + monitored_root_folders.len() + 1,
|
||||
)
|
||||
.collect(),
|
||||
);
|
||||
|
||||
let stat_item_areas = Layout::vertical(constraints).margin(1).split(area);
|
||||
|
||||
let version_paragraph = Paragraph::new(Text::from(format!(
|
||||
"Readarr Version: {}",
|
||||
app.data.readarr_data.version
|
||||
)))
|
||||
.block(borderless_block())
|
||||
.bold();
|
||||
|
||||
let uptime = Utc::now() - start_time.to_owned();
|
||||
let days = uptime.num_days();
|
||||
let day_difference = uptime - Duration::days(days);
|
||||
let hours = day_difference.num_hours();
|
||||
let hour_difference = day_difference - Duration::hours(hours);
|
||||
let minutes = hour_difference.num_minutes();
|
||||
let seconds = (hour_difference - Duration::minutes(minutes)).num_seconds();
|
||||
|
||||
let uptime_paragraph = Paragraph::new(Text::from(format!(
|
||||
"Uptime: {days}d {hours:0width$}:{minutes:0width$}:{seconds:0width$}",
|
||||
width = 2
|
||||
)))
|
||||
.block(borderless_block())
|
||||
.bold();
|
||||
|
||||
let storage = Paragraph::new(Text::from("Storage:")).block(borderless_block().bold());
|
||||
let folders = Paragraph::new(Text::from("Root Folders:")).block(borderless_block().bold());
|
||||
|
||||
f.render_widget(version_paragraph, stat_item_areas[0]);
|
||||
f.render_widget(uptime_paragraph, stat_item_areas[1]);
|
||||
f.render_widget(storage, stat_item_areas[2]);
|
||||
|
||||
for i in 0..monitored_disk_space_vec.len() {
|
||||
let DiskSpace {
|
||||
path,
|
||||
free_space,
|
||||
total_space,
|
||||
} = &monitored_disk_space_vec[i];
|
||||
let title = if let Some(path) = path {
|
||||
path
|
||||
} else {
|
||||
&format!("Disk {}", i + 1)
|
||||
};
|
||||
let ratio = if *total_space == 0 {
|
||||
0f64
|
||||
} else {
|
||||
1f64 - (*free_space as f64 / *total_space as f64)
|
||||
};
|
||||
|
||||
let space_gauge = line_gauge_with_label(title.as_str(), ratio);
|
||||
|
||||
f.render_widget(space_gauge, stat_item_areas[i + 3]);
|
||||
}
|
||||
|
||||
f.render_widget(folders, stat_item_areas[monitored_disk_space_vec.len() + 3]);
|
||||
|
||||
for i in 0..monitored_root_folders.len() {
|
||||
let RootFolder {
|
||||
path, free_space, ..
|
||||
} = &monitored_root_folders[i];
|
||||
let space: f64 = convert_to_gb(*free_space);
|
||||
let root_folder_space = Paragraph::new(format!("{path}: {space:.2} GB free"))
|
||||
.block(borderless_block())
|
||||
.default_color();
|
||||
|
||||
f.render_widget(
|
||||
root_folder_space,
|
||||
stat_item_areas[i + monitored_disk_space_vec.len() + 4],
|
||||
)
|
||||
}
|
||||
} else {
|
||||
f.render_widget(LoadingBlock::new(app.is_loading, block), area);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_downloads_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
|
||||
let block = title_block("Downloads");
|
||||
let downloads_vec = &app.data.readarr_data.downloads.items;
|
||||
|
||||
if !downloads_vec.is_empty() {
|
||||
f.render_widget(block, area);
|
||||
|
||||
let max_items = ((((area.height as f64 / 2.0).floor() * 2.0) as i64) / 2) - 1;
|
||||
let items = cmp::min(downloads_vec.len(), max_items.unsigned_abs() as usize);
|
||||
let download_item_areas =
|
||||
Layout::vertical(iter::repeat_n(Constraint::Length(2), items).collect::<Vec<Constraint>>())
|
||||
.margin(1)
|
||||
.split(area);
|
||||
|
||||
for i in 0..items {
|
||||
let DownloadRecord {
|
||||
title,
|
||||
sizeleft,
|
||||
size,
|
||||
..
|
||||
} = &downloads_vec[i];
|
||||
let percent = if *size == 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
1f64 - (*sizeleft / *size)
|
||||
};
|
||||
let download_gauge = line_gauge_with_title(title, percent);
|
||||
|
||||
f.render_widget(download_gauge, download_item_areas[i]);
|
||||
}
|
||||
} else {
|
||||
f.render_widget(LoadingBlock::new(app.is_loading, block), area);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_readarr_logo(f: &mut Frame<'_>, area: Rect) {
|
||||
let logo_text = Text::from(READARR_LOGO);
|
||||
let logo = Paragraph::new(logo_text)
|
||||
.light_magenta()
|
||||
.block(layout_block().default_color())
|
||||
.centered();
|
||||
f.render_widget(logo, area);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock;
|
||||
use crate::ui::DrawUi;
|
||||
use crate::ui::readarr_ui::ReadarrUi;
|
||||
|
||||
#[test]
|
||||
fn test_readarr_ui_accepts() {
|
||||
for readarr_block in ActiveReadarrBlock::iter() {
|
||||
assert!(ReadarrUi::accepts(Route::Readarr(readarr_block, None)));
|
||||
}
|
||||
}
|
||||
|
||||
mod snapshot_tests {
|
||||
use super::*;
|
||||
use crate::app::App;
|
||||
use crate::ui::ui_test_utils::test_utils::{TerminalSize, render_to_string_with_app};
|
||||
use rstest::rstest;
|
||||
|
||||
#[rstest]
|
||||
#[case(ActiveReadarrBlock::Authors, 0)]
|
||||
#[case(ActiveReadarrBlock::Downloads, 1)]
|
||||
#[case(ActiveReadarrBlock::Blocklist, 2)]
|
||||
#[case(ActiveReadarrBlock::History, 3)]
|
||||
#[case(ActiveReadarrBlock::RootFolders, 4)]
|
||||
#[case(ActiveReadarrBlock::Indexers, 5)]
|
||||
#[case(ActiveReadarrBlock::System, 6)]
|
||||
fn test_readarr_ui_renders_readarr_tabs(
|
||||
#[case] active_readarr_block: ActiveReadarrBlock,
|
||||
#[case] index: usize,
|
||||
) {
|
||||
let mut app = App::test_default_fully_populated();
|
||||
app.push_navigation_stack(active_readarr_block.into());
|
||||
app.data.readarr_data.main_tabs.set_index(index);
|
||||
|
||||
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||
ReadarrUi::draw(f, app, f.area());
|
||||
});
|
||||
|
||||
insta::assert_snapshot!(format!("readarr_tabs_{active_readarr_block}"), output);
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
---
|
||||
source: src/ui/readarr_ui/readarr_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Authors ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
||||
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ Name ▼ Type Status Quality Profile Metadata Profile Books Size Monitored Tags │
|
||||
│=> Test Author Continuing Standard Standard 0 0.00 GB 🏷 alex │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
---
|
||||
source: src/ui/readarr_ui/readarr_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Authors ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
---
|
||||
source: src/ui/readarr_ui/readarr_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Authors ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
---
|
||||
source: src/ui/readarr_ui/readarr_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Authors ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
---
|
||||
source: src/ui/readarr_ui/readarr_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Authors ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
---
|
||||
source: src/ui/readarr_ui/readarr_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Authors ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
---
|
||||
source: src/ui/readarr_ui/readarr_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Authors ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Radarr │ Sonarr │ Lidarr <?> to open help│
|
||||
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
|
||||
│Lidarr Version: 1.2.3.4 ││Test download title ││ ⠀⠀⠀⣠⣴⣶⡿⠻⣿⣶⣦⣄⠀⠀⠀ │
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Radarr │ Sonarr │ Lidarr <?> to open help│
|
||||
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
|
||||
│Lidarr Version: 1.2.3.4 ╭ Keybindings ──────────────────────────────────────────────────────────────────────────╮ ││ ⠀⠀⠀⣠⣴⣶⡿⠻⣿⣶⣦⣄⠀⠀⠀ │
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Radarr │ Sonarr │ Lidarr <?> to open help│
|
||||
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
╭ Error | <esc> to close ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│Some error │
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Radarr │ Sonarr │ Lidarr <?> to open help│
|
||||
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
|
||||
│Lidarr Version: 1.2.3.4 ││Test download title ││ ⠀⠀⠀⣠⣴⣶⡿⠻⣿⣶⣦⣄⠀⠀⠀ │
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Radarr │ Sonarr │ Lidarr <?> to open help│
|
||||
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
|
||||
│Radarr Version: 1.2.3.4 ││Test Download Title ││ ⠀⣠⣶⢶⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀ │
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Radarr │ Sonarr │ Lidarr <?> to open help│
|
||||
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
|
||||
│Radarr Version: 1.2.3.4 ╭ Keybindings ──────────────────────────────────────────────────────────────────────────╮ ││ ⠀⣠⣶⢶⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀ │
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Radarr │ Sonarr │ Lidarr <?> to open help│
|
||||
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
╭ Error | <esc> to close ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│Some error │
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Radarr │ Sonarr │ Lidarr <?> to open help│
|
||||
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
|
||||
│Radarr Version: 1.2.3.4 ││Test Download Title ││ ⠀⣠⣶⢶⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀ │
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Radarr │ Sonarr │ Lidarr <?> to open help│
|
||||
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
|
||||
│Radarr Version: 1.2.3.4 ││Test Download Title ││ ⠀⣠⣶⢶⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀ │
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Radarr │ Sonarr │ Lidarr <?> to open help│
|
||||
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
|
||||
│Sonarr Version: 1.2.3.4 ││Test Download Title ││ ⠀⠀⠀⣠⣴⣶⣿⣿⣿⣿⣶⣦⣄⠀⠀⠀ │
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Radarr │ Sonarr │ Lidarr <?> to open help│
|
||||
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
|
||||
│Sonarr Version: 1.2.3.4 ╭ Keybindings ──────────────────────────────────────────────────────────────────────────╮ ││ ⠀⠀⠀⣠⣴⣶⣿⣿⣿⣿⣶⣦⣄⠀⠀⠀ │
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Radarr │ Sonarr │ Lidarr <?> to open help│
|
||||
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
╭ Error | <esc> to close ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│Some error │
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Radarr │ Sonarr │ Lidarr <?> to open help│
|
||||
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
|
||||
│Sonarr Version: 1.2.3.4 ││Test Download Title ││ ⠀⠀⠀⣠⣴⣶⣿⣿⣿⣿⣶⣦⣄⠀⠀⠀ │
|
||||
|
||||
@@ -365,6 +365,13 @@ pub fn select_cli_configuration(
|
||||
config.lidarr.as_ref().expect("Lidarr config must exist")[0].clone();
|
||||
app.server_tabs.select_tab_by_config(&default_lidarr_config);
|
||||
}
|
||||
Command::Readarr(_) => {
|
||||
let default_readarr_config =
|
||||
config.readarr.as_ref().expect("Readarr config must exist")[0].clone();
|
||||
app
|
||||
.server_tabs
|
||||
.select_tab_by_config(&default_readarr_config);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user