Added full support for managing the blocklist
@@ -863,7 +863,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "managarr"
|
name = "managarr"
|
||||||
version = "0.0.33"
|
version = "0.0.34"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "managarr"
|
name = "managarr"
|
||||||
version = "0.0.33"
|
version = "0.0.34"
|
||||||
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||||
description = "A TUI to manage your Servarrs"
|
description = "A TUI to manage your Servarrs"
|
||||||
keywords = ["managarr", "tui-rs", "dashboard", "servarr", "tui"]
|
keywords = ["managarr", "tui-rs", "dashboard", "servarr", "tui"]
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ pleasant as possible!
|
|||||||
|
|
||||||
### Radarr
|
### Radarr
|
||||||
|
|
||||||
- [x] View your library, downloads, collections
|
- [x] View your library, downloads, collections, and blocklist
|
||||||
- [x] View details of a specific movie including description, history, downloaded file info, or the credits
|
- [x] View details of a specific movie including description, history, downloaded file info, or the credits
|
||||||
- [x] View details of any collection and the movies in them
|
- [x] View details of any collection and the movies in them
|
||||||
- [x] Search your library or collections
|
- [x] Search your library or collections
|
||||||
@@ -48,6 +48,7 @@ pleasant as possible!
|
|||||||
- [x] Edit your movies, collections, and indexers
|
- [x] Edit your movies, collections, and indexers
|
||||||
- [x] Manage your tags
|
- [x] Manage your tags
|
||||||
- [x] Manage your root folders
|
- [x] Manage your root folders
|
||||||
|
- [x] Manage your blocklist
|
||||||
- [ ] Manage your quality profiles
|
- [ ] Manage your quality profiles
|
||||||
- [ ] Manage your quality definitions
|
- [ ] Manage your quality definitions
|
||||||
- [x] View and browse logs, tasks, events queues, and updates
|
- [x] View and browse logs, tasks, events queues, and updates
|
||||||
@@ -144,6 +145,8 @@ tautulli:
|
|||||||

|

|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
* [ratatui](https://github.com/tui-rs-revival/ratatui)
|
* [ratatui](https://github.com/tui-rs-revival/ratatui)
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 212 KiB After Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 363 KiB After Width: | Height: | Size: 374 KiB |
|
Before Width: | Height: | Size: 344 KiB After Width: | Height: | Size: 382 KiB |
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 176 KiB |
@@ -15,6 +15,7 @@ generate_keybindings! {
|
|||||||
left,
|
left,
|
||||||
right,
|
right,
|
||||||
backspace,
|
backspace,
|
||||||
|
clear,
|
||||||
search,
|
search,
|
||||||
settings,
|
settings,
|
||||||
filter,
|
filter,
|
||||||
@@ -67,6 +68,10 @@ pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
|
|||||||
key: Key::Backspace,
|
key: Key::Backspace,
|
||||||
desc: "backspace",
|
desc: "backspace",
|
||||||
},
|
},
|
||||||
|
clear: KeyBinding {
|
||||||
|
key: Key::Char('c'),
|
||||||
|
desc: "clear",
|
||||||
|
},
|
||||||
search: KeyBinding {
|
search: KeyBinding {
|
||||||
key: Key::Char('s'),
|
key: Key::Char('s'),
|
||||||
desc: "search",
|
desc: "search",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ mod test {
|
|||||||
#[case(DEFAULT_KEYBINDINGS.left, Key::Left, "left")]
|
#[case(DEFAULT_KEYBINDINGS.left, Key::Left, "left")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.right, Key::Right, "right")]
|
#[case(DEFAULT_KEYBINDINGS.right, Key::Right, "right")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.backspace, Key::Backspace, "backspace")]
|
#[case(DEFAULT_KEYBINDINGS.backspace, Key::Backspace, "backspace")]
|
||||||
|
#[case(DEFAULT_KEYBINDINGS.clear, Key::Char('c'), "clear")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.search, Key::Char('s'), "search")]
|
#[case(DEFAULT_KEYBINDINGS.search, Key::Char('s'), "search")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.settings, Key::Char('s'), "settings")]
|
#[case(DEFAULT_KEYBINDINGS.settings, Key::Char('s'), "settings")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.filter, Key::Char('f'), "filter")]
|
#[case(DEFAULT_KEYBINDINGS.filter, Key::Char('f'), "filter")]
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ mod radarr_tests;
|
|||||||
impl<'a> App<'a> {
|
impl<'a> App<'a> {
|
||||||
pub(super) async fn dispatch_by_radarr_block(&mut self, active_radarr_block: &ActiveRadarrBlock) {
|
pub(super) async fn dispatch_by_radarr_block(&mut self, active_radarr_block: &ActiveRadarrBlock) {
|
||||||
match active_radarr_block {
|
match active_radarr_block {
|
||||||
|
ActiveRadarrBlock::Blocklist => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(RadarrEvent::GetBlocklist.into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
ActiveRadarrBlock::Collections => {
|
ActiveRadarrBlock::Collections => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(RadarrEvent::GetCollections.into())
|
.dispatch_network_event(RadarrEvent::GetCollections.into())
|
||||||
|
|||||||
@@ -21,14 +21,6 @@ pub static LIBRARY_CONTEXT_CLUES: [ContextClue; 10] = [
|
|||||||
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
|
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static DOWNLOADS_CONTEXT_CLUES: [ContextClue; 2] = [
|
|
||||||
(
|
|
||||||
DEFAULT_KEYBINDINGS.refresh,
|
|
||||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
|
||||||
),
|
|
||||||
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
|
||||||
];
|
|
||||||
|
|
||||||
pub static COLLECTIONS_CONTEXT_CLUES: [ContextClue; 8] = [
|
pub static COLLECTIONS_CONTEXT_CLUES: [ContextClue; 8] = [
|
||||||
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||||
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
|
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
|
||||||
@@ -43,6 +35,25 @@ pub static COLLECTIONS_CONTEXT_CLUES: [ContextClue; 8] = [
|
|||||||
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
|
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub static DOWNLOADS_CONTEXT_CLUES: [ContextClue; 2] = [
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub static BLOCKLIST_CONTEXT_CLUES: [ContextClue; 5] = [
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||||
|
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.clear, "clear blocklist"),
|
||||||
|
];
|
||||||
|
|
||||||
pub static ROOT_FOLDERS_CONTEXT_CLUES: [ContextClue; 3] = [
|
pub static ROOT_FOLDERS_CONTEXT_CLUES: [ContextClue; 3] = [
|
||||||
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
|
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
|
||||||
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
||||||
@@ -52,8 +63,7 @@ pub static ROOT_FOLDERS_CONTEXT_CLUES: [ContextClue; 3] = [
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static INDEXERS_CONTEXT_CLUES: [ContextClue; 7] = [
|
pub static INDEXERS_CONTEXT_CLUES: [ContextClue; 6] = [
|
||||||
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
|
|
||||||
(DEFAULT_KEYBINDINGS.submit, "edit indexer"),
|
(DEFAULT_KEYBINDINGS.submit, "edit indexer"),
|
||||||
(
|
(
|
||||||
DEFAULT_KEYBINDINGS.settings,
|
DEFAULT_KEYBINDINGS.settings,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ mod tests {
|
|||||||
|
|
||||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
use crate::app::radarr::radarr_context_clues::{
|
use crate::app::radarr::radarr_context_clues::{
|
||||||
ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES, COLLECTIONS_CONTEXT_CLUES,
|
ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, COLLECTIONS_CONTEXT_CLUES,
|
||||||
COLLECTION_DETAILS_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
COLLECTION_DETAILS_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
||||||
LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
|
LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
|
||||||
MANUAL_MOVIE_SEARCH_CONTEXT_CLUES, MOVIE_DETAILS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES,
|
MANUAL_MOVIE_SEARCH_CONTEXT_CLUES, MOVIE_DETAILS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES,
|
||||||
@@ -67,22 +67,6 @@ mod tests {
|
|||||||
assert_eq!(library_context_clues_iter.next(), None);
|
assert_eq!(library_context_clues_iter.next(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_downloads_context_clues() {
|
|
||||||
let mut downloads_context_clues_iter = DOWNLOADS_CONTEXT_CLUES.iter();
|
|
||||||
|
|
||||||
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
|
||||||
|
|
||||||
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
|
|
||||||
assert_eq!(downloads_context_clues_iter.next(), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collections_context_clues() {
|
fn test_collections_context_clues() {
|
||||||
let mut collections_context_clues = COLLECTIONS_CONTEXT_CLUES.iter();
|
let mut collections_context_clues = COLLECTIONS_CONTEXT_CLUES.iter();
|
||||||
@@ -129,6 +113,53 @@ mod tests {
|
|||||||
assert_eq!(collections_context_clues.next(), None);
|
assert_eq!(collections_context_clues.next(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_downloads_context_clues() {
|
||||||
|
let mut downloads_context_clues_iter = DOWNLOADS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
|
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
||||||
|
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
||||||
|
|
||||||
|
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
||||||
|
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
|
||||||
|
assert_eq!(downloads_context_clues_iter.next(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_context_clues() {
|
||||||
|
let mut blocklist_context_clues_iter = BLOCKLIST_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
|
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
||||||
|
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
||||||
|
|
||||||
|
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
||||||
|
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
||||||
|
|
||||||
|
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
||||||
|
assert_str_eq!(*description, "details");
|
||||||
|
|
||||||
|
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
||||||
|
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
|
||||||
|
|
||||||
|
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.clear);
|
||||||
|
assert_str_eq!(*description, "clear blocklist");
|
||||||
|
assert_eq!(blocklist_context_clues_iter.next(), None);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_root_folders_context_clues() {
|
fn test_root_folders_context_clues() {
|
||||||
let mut root_folders_context_clues_iter = ROOT_FOLDERS_CONTEXT_CLUES.iter();
|
let mut root_folders_context_clues_iter = ROOT_FOLDERS_CONTEXT_CLUES.iter();
|
||||||
@@ -156,11 +187,6 @@ mod tests {
|
|||||||
|
|
||||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.add);
|
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.add.desc);
|
|
||||||
|
|
||||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
||||||
assert_str_eq!(*description, "edit indexer");
|
assert_str_eq!(*description, "edit indexer");
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,23 @@ mod tests {
|
|||||||
use crate::network::radarr_network::RadarrEvent;
|
use crate::network::radarr_network::RadarrEvent;
|
||||||
use crate::network::NetworkEvent;
|
use crate::network::NetworkEvent;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_blocklist_block() {
|
||||||
|
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_radarr_block(&ActiveRadarrBlock::Blocklist)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(
|
||||||
|
sync_network_rx.recv().await.unwrap(),
|
||||||
|
RadarrEvent::GetBlocklist.into()
|
||||||
|
);
|
||||||
|
assert!(!app.data.radarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_collections_block() {
|
async fn test_dispatch_by_collections_block() {
|
||||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||||
|
|||||||
@@ -0,0 +1,707 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::radarr_handlers::blocklist::{blocklist_sorting_options, BlocklistHandler};
|
||||||
|
use crate::handlers::KeyEventHandler;
|
||||||
|
use crate::models::radarr_models::{BlocklistItem, Language, Movie, Quality, QualityWrapper};
|
||||||
|
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS};
|
||||||
|
use crate::models::stateful_table::SortOption;
|
||||||
|
use chrono::DateTime;
|
||||||
|
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
mod test_handle_scroll_up_and_down {
|
||||||
|
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use crate::models::radarr_models::BlocklistItem;
|
||||||
|
use crate::{simple_stateful_iterable_vec, test_iterable_scroll};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
test_iterable_scroll!(
|
||||||
|
test_blocklist_scroll,
|
||||||
|
BlocklistHandler,
|
||||||
|
blocklist,
|
||||||
|
simple_stateful_iterable_vec!(BlocklistItem, String, source_title),
|
||||||
|
ActiveRadarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
source_title,
|
||||||
|
to_string
|
||||||
|
);
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_blocklist_sort_scroll(
|
||||||
|
#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key,
|
||||||
|
) {
|
||||||
|
let blocklist_field_vec = sort_options();
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.radarr_data.blocklist.sorting(sort_options());
|
||||||
|
|
||||||
|
if key == Key::Up {
|
||||||
|
for i in (0..blocklist_field_vec.len()).rev() {
|
||||||
|
BlocklistHandler::with(
|
||||||
|
&key,
|
||||||
|
&mut app,
|
||||||
|
&ActiveRadarrBlock::BlocklistSortPrompt,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.sort
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.current_selection(),
|
||||||
|
&blocklist_field_vec[i]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i in 0..blocklist_field_vec.len() {
|
||||||
|
BlocklistHandler::with(
|
||||||
|
&key,
|
||||||
|
&mut app,
|
||||||
|
&ActiveRadarrBlock::BlocklistSortPrompt,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.sort
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.current_selection(),
|
||||||
|
&blocklist_field_vec[(i + 1) % blocklist_field_vec.len()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_home_end {
|
||||||
|
use super::*;
|
||||||
|
use crate::models::radarr_models::BlocklistItem;
|
||||||
|
use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end};
|
||||||
|
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||||
|
|
||||||
|
test_iterable_home_and_end!(
|
||||||
|
test_blocklist_home_and_end,
|
||||||
|
BlocklistHandler,
|
||||||
|
blocklist,
|
||||||
|
extended_stateful_iterable_vec!(BlocklistItem, String, source_title),
|
||||||
|
ActiveRadarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
source_title,
|
||||||
|
to_string
|
||||||
|
);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_sort_home_end() {
|
||||||
|
let blocklist_field_vec = sort_options();
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.radarr_data.blocklist.sorting(sort_options());
|
||||||
|
|
||||||
|
BlocklistHandler::with(
|
||||||
|
&DEFAULT_KEYBINDINGS.end.key,
|
||||||
|
&mut app,
|
||||||
|
&ActiveRadarrBlock::BlocklistSortPrompt,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.sort
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.current_selection(),
|
||||||
|
&blocklist_field_vec[blocklist_field_vec.len() - 1]
|
||||||
|
);
|
||||||
|
|
||||||
|
BlocklistHandler::with(
|
||||||
|
&DEFAULT_KEYBINDINGS.home.key,
|
||||||
|
&mut app,
|
||||||
|
&ActiveRadarrBlock::BlocklistSortPrompt,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.sort
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.current_selection(),
|
||||||
|
&blocklist_field_vec[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_delete {
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_delete_prompt;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_blocklist_item_prompt() {
|
||||||
|
assert_delete_prompt!(
|
||||||
|
BlocklistHandler,
|
||||||
|
ActiveRadarrBlock::Blocklist,
|
||||||
|
ActiveRadarrBlock::DeleteBlocklistItemPrompt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_left_right_action {
|
||||||
|
use super::*;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_tab_left() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.radarr_data.main_tabs.set_index(3);
|
||||||
|
|
||||||
|
BlocklistHandler::with(
|
||||||
|
&DEFAULT_KEYBINDINGS.left.key,
|
||||||
|
&mut app,
|
||||||
|
&ActiveRadarrBlock::Blocklist,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.radarr_data.main_tabs.get_active_route(),
|
||||||
|
&ActiveRadarrBlock::Downloads.into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
&ActiveRadarrBlock::Downloads.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_tab_right() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.radarr_data.main_tabs.set_index(3);
|
||||||
|
|
||||||
|
BlocklistHandler::with(
|
||||||
|
&DEFAULT_KEYBINDINGS.right.key,
|
||||||
|
&mut app,
|
||||||
|
&ActiveRadarrBlock::Blocklist,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.radarr_data.main_tabs.get_active_route(),
|
||||||
|
&ActiveRadarrBlock::RootFolders.into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
&ActiveRadarrBlock::RootFolders.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_blocklist_left_right_prompt_toggle(
|
||||||
|
#[values(
|
||||||
|
ActiveRadarrBlock::DeleteBlocklistItemPrompt,
|
||||||
|
ActiveRadarrBlock::BlocklistClearAllItemsPrompt
|
||||||
|
)]
|
||||||
|
active_radarr_block: ActiveRadarrBlock,
|
||||||
|
#[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key,
|
||||||
|
) {
|
||||||
|
let mut app = App::default();
|
||||||
|
|
||||||
|
BlocklistHandler::with(&key, &mut app, &active_radarr_block, &None).handle();
|
||||||
|
|
||||||
|
assert!(app.data.radarr_data.prompt_confirm);
|
||||||
|
|
||||||
|
BlocklistHandler::with(&key, &mut app, &active_radarr_block, &None).handle();
|
||||||
|
|
||||||
|
assert!(!app.data.radarr_data.prompt_confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_submit {
|
||||||
|
use crate::network::radarr_network::RadarrEvent;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(
|
||||||
|
ActiveRadarrBlock::Blocklist,
|
||||||
|
ActiveRadarrBlock::DeleteBlocklistItemPrompt,
|
||||||
|
RadarrEvent::DeleteBlocklistItem
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
ActiveRadarrBlock::Blocklist,
|
||||||
|
ActiveRadarrBlock::BlocklistClearAllItemsPrompt,
|
||||||
|
RadarrEvent::ClearBlocklist
|
||||||
|
)]
|
||||||
|
fn test_blocklist_prompt_confirm_submit(
|
||||||
|
#[case] base_route: ActiveRadarrBlock,
|
||||||
|
#[case] prompt_block: ActiveRadarrBlock,
|
||||||
|
#[case] expected_action: RadarrEvent,
|
||||||
|
) {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.radarr_data.prompt_confirm = true;
|
||||||
|
app.push_navigation_stack(base_route.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
BlocklistHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle();
|
||||||
|
|
||||||
|
assert!(app.data.radarr_data.prompt_confirm);
|
||||||
|
assert_eq!(
|
||||||
|
app.data.radarr_data.prompt_confirm_action,
|
||||||
|
Some(expected_action)
|
||||||
|
);
|
||||||
|
assert_eq!(app.get_current_route(), &base_route.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_blocklist_prompt_decline_submit(
|
||||||
|
#[values(
|
||||||
|
ActiveRadarrBlock::DeleteBlocklistItemPrompt,
|
||||||
|
ActiveRadarrBlock::BlocklistClearAllItemsPrompt
|
||||||
|
)]
|
||||||
|
prompt_block: ActiveRadarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
BlocklistHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle();
|
||||||
|
|
||||||
|
assert!(!app.data.radarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
&ActiveRadarrBlock::Blocklist.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_sort_prompt_submit() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.radarr_data.blocklist.sort_asc = true;
|
||||||
|
app.data.radarr_data.blocklist.sorting(sort_options());
|
||||||
|
app.data.radarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::BlocklistSortPrompt.into());
|
||||||
|
|
||||||
|
let mut expected_vec = blocklist_vec();
|
||||||
|
expected_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
||||||
|
expected_vec.reverse();
|
||||||
|
|
||||||
|
BlocklistHandler::with(
|
||||||
|
&SUBMIT_KEY,
|
||||||
|
&mut app,
|
||||||
|
&ActiveRadarrBlock::BlocklistSortPrompt,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
&ActiveRadarrBlock::Blocklist.into()
|
||||||
|
);
|
||||||
|
assert_eq!(app.data.radarr_data.blocklist.items, expected_vec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_esc {
|
||||||
|
use super::*;
|
||||||
|
use crate::handlers::radarr_handlers::downloads::DownloadsHandler;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(
|
||||||
|
ActiveRadarrBlock::Blocklist,
|
||||||
|
ActiveRadarrBlock::DeleteBlocklistItemPrompt
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
ActiveRadarrBlock::Blocklist,
|
||||||
|
ActiveRadarrBlock::BlocklistClearAllItemsPrompt
|
||||||
|
)]
|
||||||
|
fn test_blocklist_prompt_blocks_esc(
|
||||||
|
#[case] base_block: ActiveRadarrBlock,
|
||||||
|
#[case] prompt_block: ActiveRadarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.push_navigation_stack(base_block.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
app.data.radarr_data.prompt_confirm = true;
|
||||||
|
|
||||||
|
BlocklistHandler::with(&ESC_KEY, &mut app, &prompt_block, &None).handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), &base_block.into());
|
||||||
|
assert!(!app.data.radarr_data.prompt_confirm);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_esc_blocklist_item_details() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::BlocklistItemDetails.into());
|
||||||
|
|
||||||
|
BlocklistHandler::with(
|
||||||
|
&ESC_KEY,
|
||||||
|
&mut app,
|
||||||
|
&ActiveRadarrBlock::BlocklistItemDetails,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
&ActiveRadarrBlock::Blocklist.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_sort_prompt_block_esc() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::BlocklistSortPrompt.into());
|
||||||
|
|
||||||
|
BlocklistHandler::with(
|
||||||
|
&ESC_KEY,
|
||||||
|
&mut app,
|
||||||
|
&ActiveRadarrBlock::BlocklistSortPrompt,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
&ActiveRadarrBlock::Blocklist.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_esc() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.error = "test error".to_owned().into();
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
|
||||||
|
|
||||||
|
DownloadsHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Blocklist, &None).handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
&ActiveRadarrBlock::Blocklist.into()
|
||||||
|
);
|
||||||
|
assert!(app.error.text.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_key_char {
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_refresh_key;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refresh_blocklist_key() {
|
||||||
|
assert_refresh_key!(BlocklistHandler, ActiveRadarrBlock::Blocklist);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clear_blocklist_key() {
|
||||||
|
let mut app = App::default();
|
||||||
|
|
||||||
|
BlocklistHandler::with(
|
||||||
|
&DEFAULT_KEYBINDINGS.clear.key,
|
||||||
|
&mut app,
|
||||||
|
&ActiveRadarrBlock::Blocklist,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
&ActiveRadarrBlock::BlocklistClearAllItemsPrompt.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sort_key() {
|
||||||
|
let mut app = App::default();
|
||||||
|
|
||||||
|
BlocklistHandler::with(
|
||||||
|
&DEFAULT_KEYBINDINGS.sort.key,
|
||||||
|
&mut app,
|
||||||
|
&ActiveRadarrBlock::Blocklist,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
&ActiveRadarrBlock::BlocklistSortPrompt.into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
app.data.radarr_data.blocklist.sort.as_ref().unwrap().items,
|
||||||
|
blocklist_sorting_options()
|
||||||
|
);
|
||||||
|
assert!(!app.data.radarr_data.blocklist.sort_asc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_sorting_options_movie_title() {
|
||||||
|
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering = |a, b| {
|
||||||
|
a.movie
|
||||||
|
.title
|
||||||
|
.text
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.movie.title.text.to_lowercase())
|
||||||
|
};
|
||||||
|
let mut expected_blocklist_vec = blocklist_vec();
|
||||||
|
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = blocklist_sorting_options()[0].clone();
|
||||||
|
let mut sorted_blocklist_vec = blocklist_vec();
|
||||||
|
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Movie Title");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_sorting_options_source_title() {
|
||||||
|
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering = |a, b| {
|
||||||
|
a.source_title
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.source_title.to_lowercase())
|
||||||
|
};
|
||||||
|
let mut expected_blocklist_vec = blocklist_vec();
|
||||||
|
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = blocklist_sorting_options()[1].clone();
|
||||||
|
let mut sorted_blocklist_vec = blocklist_vec();
|
||||||
|
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Source Title");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_sorting_options_languages() {
|
||||||
|
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering = |a, b| {
|
||||||
|
let a_languages = a
|
||||||
|
.languages
|
||||||
|
.iter()
|
||||||
|
.map(|lang| lang.name.to_lowercase())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
let b_languages = b
|
||||||
|
.languages
|
||||||
|
.iter()
|
||||||
|
.map(|lang| lang.name.to_lowercase())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
a_languages.cmp(&b_languages)
|
||||||
|
};
|
||||||
|
let mut expected_blocklist_vec = blocklist_vec();
|
||||||
|
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = blocklist_sorting_options()[2].clone();
|
||||||
|
let mut sorted_blocklist_vec = blocklist_vec();
|
||||||
|
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Languages");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_sorting_options_quality() {
|
||||||
|
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering = |a, b| {
|
||||||
|
a.quality
|
||||||
|
.quality
|
||||||
|
.name
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.quality.quality.name.to_lowercase())
|
||||||
|
};
|
||||||
|
let mut expected_blocklist_vec = blocklist_vec();
|
||||||
|
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = blocklist_sorting_options()[3].clone();
|
||||||
|
let mut sorted_blocklist_vec = blocklist_vec();
|
||||||
|
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Quality");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_sorting_options_custom_formats() {
|
||||||
|
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering = |a, b| {
|
||||||
|
let a_custom_formats = a
|
||||||
|
.custom_formats
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&Vec::new())
|
||||||
|
.iter()
|
||||||
|
.map(|lang| lang.name.to_lowercase())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
let b_custom_formats = b
|
||||||
|
.custom_formats
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&Vec::new())
|
||||||
|
.iter()
|
||||||
|
.map(|lang| lang.name.to_lowercase())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
a_custom_formats.cmp(&b_custom_formats)
|
||||||
|
};
|
||||||
|
let mut expected_blocklist_vec = blocklist_vec();
|
||||||
|
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = blocklist_sorting_options()[4].clone();
|
||||||
|
let mut sorted_blocklist_vec = blocklist_vec();
|
||||||
|
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Formats");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_sorting_options_date() {
|
||||||
|
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering =
|
||||||
|
|a, b| a.date.cmp(&b.date);
|
||||||
|
let mut expected_blocklist_vec = blocklist_vec();
|
||||||
|
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = blocklist_sorting_options()[5].clone();
|
||||||
|
let mut sorted_blocklist_vec = blocklist_vec();
|
||||||
|
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Date");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blocklist_vec() -> Vec<BlocklistItem> {
|
||||||
|
vec![
|
||||||
|
BlocklistItem {
|
||||||
|
id: 3,
|
||||||
|
source_title: "test 1".to_owned(),
|
||||||
|
languages: vec![Language {
|
||||||
|
name: "telgu".to_owned(),
|
||||||
|
}],
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "HD - 1080p".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
custom_formats: Some(vec![Language {
|
||||||
|
name: "nikki".to_owned(),
|
||||||
|
}]),
|
||||||
|
date: DateTime::from(DateTime::parse_from_rfc3339("2024-01-10T07:28:45Z").unwrap()),
|
||||||
|
movie: Movie {
|
||||||
|
title: "test 3".into(),
|
||||||
|
..Movie::default()
|
||||||
|
},
|
||||||
|
..BlocklistItem::default()
|
||||||
|
},
|
||||||
|
BlocklistItem {
|
||||||
|
id: 2,
|
||||||
|
source_title: "test 2".to_owned(),
|
||||||
|
languages: vec![Language {
|
||||||
|
name: "chinese".to_owned(),
|
||||||
|
}],
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "SD - 720p".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
custom_formats: Some(vec![
|
||||||
|
Language {
|
||||||
|
name: "alex".to_owned(),
|
||||||
|
},
|
||||||
|
Language {
|
||||||
|
name: "English".to_owned(),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
date: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()),
|
||||||
|
movie: Movie {
|
||||||
|
title: "test 2".into(),
|
||||||
|
..Movie::default()
|
||||||
|
},
|
||||||
|
..BlocklistItem::default()
|
||||||
|
},
|
||||||
|
BlocklistItem {
|
||||||
|
id: 1,
|
||||||
|
source_title: "test 3".to_owned(),
|
||||||
|
languages: vec![Language {
|
||||||
|
name: "english".to_owned(),
|
||||||
|
}],
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "HD - 1080p".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
custom_formats: Some(vec![Language {
|
||||||
|
name: "English".to_owned(),
|
||||||
|
}]),
|
||||||
|
date: DateTime::from(DateTime::parse_from_rfc3339("2024-03-10T07:28:45Z").unwrap()),
|
||||||
|
movie: Movie {
|
||||||
|
title: "test 1".into(),
|
||||||
|
..Movie::default()
|
||||||
|
},
|
||||||
|
..BlocklistItem::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort_options() -> Vec<SortOption<BlocklistItem>> {
|
||||||
|
vec![SortOption {
|
||||||
|
name: "Test 1",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
b.source_title
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&a.source_title.to_lowercase())
|
||||||
|
}),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_handler_accepts() {
|
||||||
|
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
|
||||||
|
if BLOCKLIST_BLOCKS.contains(&active_radarr_block) {
|
||||||
|
assert!(BlocklistHandler::accepts(&active_radarr_block));
|
||||||
|
} else {
|
||||||
|
assert!(!BlocklistHandler::accepts(&active_radarr_block));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,284 @@
|
|||||||
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
||||||
|
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
|
||||||
|
use crate::models::radarr_models::BlocklistItem;
|
||||||
|
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS};
|
||||||
|
use crate::models::stateful_table::SortOption;
|
||||||
|
use crate::models::Scrollable;
|
||||||
|
use crate::network::radarr_network::RadarrEvent;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "blocklist_handler_tests.rs"]
|
||||||
|
mod blocklist_handler_tests;
|
||||||
|
|
||||||
|
pub(super) struct BlocklistHandler<'a, 'b> {
|
||||||
|
key: &'a Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_radarr_block: &'a ActiveRadarrBlock,
|
||||||
|
_context: &'a Option<ActiveRadarrBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a, 'b> {
|
||||||
|
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
|
||||||
|
BLOCKLIST_BLOCKS.contains(active_block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with(
|
||||||
|
key: &'a Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_block: &'a ActiveRadarrBlock,
|
||||||
|
context: &'a Option<ActiveRadarrBlock>,
|
||||||
|
) -> Self {
|
||||||
|
BlocklistHandler {
|
||||||
|
key,
|
||||||
|
app,
|
||||||
|
active_radarr_block: active_block,
|
||||||
|
_context: context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key(&self) -> &Key {
|
||||||
|
self.key
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_scroll_up(&mut self) {
|
||||||
|
match self.active_radarr_block {
|
||||||
|
ActiveRadarrBlock::Blocklist => self.app.data.radarr_data.blocklist.scroll_up(),
|
||||||
|
ActiveRadarrBlock::BlocklistSortPrompt => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.sort
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.scroll_up(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_scroll_down(&mut self) {
|
||||||
|
match self.active_radarr_block {
|
||||||
|
ActiveRadarrBlock::Blocklist => self.app.data.radarr_data.blocklist.scroll_down(),
|
||||||
|
ActiveRadarrBlock::BlocklistSortPrompt => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.sort
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.scroll_down(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_home(&mut self) {
|
||||||
|
match self.active_radarr_block {
|
||||||
|
ActiveRadarrBlock::Blocklist => self.app.data.radarr_data.blocklist.scroll_to_top(),
|
||||||
|
ActiveRadarrBlock::BlocklistSortPrompt => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.sort
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.scroll_to_top(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_end(&mut self) {
|
||||||
|
match self.active_radarr_block {
|
||||||
|
ActiveRadarrBlock::Blocklist => self.app.data.radarr_data.blocklist.scroll_to_bottom(),
|
||||||
|
ActiveRadarrBlock::BlocklistSortPrompt => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.sort
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.scroll_to_bottom(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_delete(&mut self) {
|
||||||
|
if self.active_radarr_block == &ActiveRadarrBlock::Blocklist {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveRadarrBlock::DeleteBlocklistItemPrompt.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_left_right_action(&mut self) {
|
||||||
|
match self.active_radarr_block {
|
||||||
|
ActiveRadarrBlock::Blocklist => handle_change_tab_left_right_keys(self.app, self.key),
|
||||||
|
ActiveRadarrBlock::DeleteBlocklistItemPrompt
|
||||||
|
| ActiveRadarrBlock::BlocklistClearAllItemsPrompt => handle_prompt_toggle(self.app, self.key),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_submit(&mut self) {
|
||||||
|
match self.active_radarr_block {
|
||||||
|
ActiveRadarrBlock::DeleteBlocklistItemPrompt => {
|
||||||
|
if self.app.data.radarr_data.prompt_confirm {
|
||||||
|
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteBlocklistItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveRadarrBlock::BlocklistClearAllItemsPrompt => {
|
||||||
|
if self.app.data.radarr_data.prompt_confirm {
|
||||||
|
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::ClearBlocklist);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveRadarrBlock::BlocklistSortPrompt => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.items
|
||||||
|
.sort_by(|a, b| a.id.cmp(&b.id));
|
||||||
|
self.app.data.radarr_data.blocklist.apply_sorting();
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveRadarrBlock::Blocklist => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveRadarrBlock::BlocklistItemDetails.into());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_esc(&mut self) {
|
||||||
|
match self.active_radarr_block {
|
||||||
|
ActiveRadarrBlock::DeleteBlocklistItemPrompt
|
||||||
|
| ActiveRadarrBlock::BlocklistClearAllItemsPrompt => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.radarr_data.prompt_confirm = false;
|
||||||
|
}
|
||||||
|
ActiveRadarrBlock::BlocklistItemDetails | ActiveRadarrBlock::BlocklistSortPrompt => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
_ => handle_clear_errors(self.app),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_char_key_event(&mut self) {
|
||||||
|
let key = self.key;
|
||||||
|
if self.active_radarr_block == &ActiveRadarrBlock::Blocklist {
|
||||||
|
match self.key {
|
||||||
|
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||||
|
self.app.should_refresh = true;
|
||||||
|
}
|
||||||
|
_ if *key == DEFAULT_KEYBINDINGS.clear.key => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveRadarrBlock::BlocklistClearAllItemsPrompt.into());
|
||||||
|
}
|
||||||
|
_ if *key == DEFAULT_KEYBINDINGS.sort.key => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.sorting(blocklist_sorting_options());
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveRadarrBlock::BlocklistSortPrompt.into());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blocklist_sorting_options() -> Vec<SortOption<BlocklistItem>> {
|
||||||
|
vec![
|
||||||
|
SortOption {
|
||||||
|
name: "Movie Title",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
a.movie
|
||||||
|
.title
|
||||||
|
.text
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.movie.title.text.to_lowercase())
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Source Title",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
a.source_title
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.source_title.to_lowercase())
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Languages",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
let a_languages = a
|
||||||
|
.languages
|
||||||
|
.iter()
|
||||||
|
.map(|lang| lang.name.to_lowercase())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
let b_languages = b
|
||||||
|
.languages
|
||||||
|
.iter()
|
||||||
|
.map(|lang| lang.name.to_lowercase())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
a_languages.cmp(&b_languages)
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Quality",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
a.quality
|
||||||
|
.quality
|
||||||
|
.name
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.quality.quality.name.to_lowercase())
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Formats",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
let a_custom_formats = a
|
||||||
|
.custom_formats
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&Vec::new())
|
||||||
|
.iter()
|
||||||
|
.map(|lang| lang.name.to_lowercase())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
let b_custom_formats = b
|
||||||
|
.custom_formats
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&Vec::new())
|
||||||
|
.iter()
|
||||||
|
.map(|lang| lang.name.to_lowercase())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
a_custom_formats.cmp(&b_custom_formats)
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Date",
|
||||||
|
cmp_fn: Some(|a, b| a.date.cmp(&b.date)),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -267,7 +267,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_collections_tab_left() {
|
fn test_collections_tab_left() {
|
||||||
let mut app = App::default();
|
let mut app = App::default();
|
||||||
app.data.radarr_data.main_tabs.set_index(2);
|
app.data.radarr_data.main_tabs.set_index(1);
|
||||||
|
|
||||||
CollectionsHandler::with(
|
CollectionsHandler::with(
|
||||||
&DEFAULT_KEYBINDINGS.left.key,
|
&DEFAULT_KEYBINDINGS.left.key,
|
||||||
@@ -279,18 +279,15 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.data.radarr_data.main_tabs.get_active_route(),
|
app.data.radarr_data.main_tabs.get_active_route(),
|
||||||
&ActiveRadarrBlock::Downloads.into()
|
&ActiveRadarrBlock::Movies.into()
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
app.get_current_route(),
|
|
||||||
&ActiveRadarrBlock::Downloads.into()
|
|
||||||
);
|
);
|
||||||
|
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collections_tab_right() {
|
fn test_collections_tab_right() {
|
||||||
let mut app = App::default();
|
let mut app = App::default();
|
||||||
app.data.radarr_data.main_tabs.set_index(2);
|
app.data.radarr_data.main_tabs.set_index(1);
|
||||||
|
|
||||||
CollectionsHandler::with(
|
CollectionsHandler::with(
|
||||||
&DEFAULT_KEYBINDINGS.right.key,
|
&DEFAULT_KEYBINDINGS.right.key,
|
||||||
@@ -302,11 +299,11 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.data.radarr_data.main_tabs.get_active_route(),
|
app.data.radarr_data.main_tabs.get_active_route(),
|
||||||
&ActiveRadarrBlock::RootFolders.into()
|
&ActiveRadarrBlock::Downloads.into()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.get_current_route(),
|
app.get_current_route(),
|
||||||
&ActiveRadarrBlock::RootFolders.into()
|
&ActiveRadarrBlock::Downloads.into()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -832,6 +829,26 @@ mod tests {
|
|||||||
assert!(!app.data.radarr_data.prompt_confirm);
|
assert!(!app.data.radarr_data.prompt_confirm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_collections_sort_prompt_block_esc() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::CollectionsSortPrompt.into());
|
||||||
|
|
||||||
|
CollectionsHandler::with(
|
||||||
|
&ESC_KEY,
|
||||||
|
&mut app,
|
||||||
|
&ActiveRadarrBlock::CollectionsSortPrompt,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
&ActiveRadarrBlock::Collections.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_esc() {
|
fn test_default_esc() {
|
||||||
let mut app = App::default();
|
let mut app = App::default();
|
||||||
|
|||||||
@@ -287,6 +287,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
|
|||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
self.app.data.radarr_data.prompt_confirm = false;
|
self.app.data.radarr_data.prompt_confirm = false;
|
||||||
}
|
}
|
||||||
|
ActiveRadarrBlock::CollectionsSortPrompt => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.app.data.radarr_data.collections.reset_search();
|
self.app.data.radarr_data.collections.reset_search();
|
||||||
self.app.data.radarr_data.collections.reset_filter();
|
self.app.data.radarr_data.collections.reset_filter();
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_downloads_tab_left() {
|
fn test_downloads_tab_left() {
|
||||||
let mut app = App::default();
|
let mut app = App::default();
|
||||||
app.data.radarr_data.main_tabs.set_index(1);
|
app.data.radarr_data.main_tabs.set_index(2);
|
||||||
|
|
||||||
DownloadsHandler::with(
|
DownloadsHandler::with(
|
||||||
&DEFAULT_KEYBINDINGS.left.key,
|
&DEFAULT_KEYBINDINGS.left.key,
|
||||||
@@ -84,26 +84,6 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.handle();
|
.handle();
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
app.data.radarr_data.main_tabs.get_active_route(),
|
|
||||||
&ActiveRadarrBlock::Movies.into()
|
|
||||||
);
|
|
||||||
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_downloads_tab_right() {
|
|
||||||
let mut app = App::default();
|
|
||||||
app.data.radarr_data.main_tabs.set_index(1);
|
|
||||||
|
|
||||||
DownloadsHandler::with(
|
|
||||||
&DEFAULT_KEYBINDINGS.right.key,
|
|
||||||
&mut app,
|
|
||||||
&ActiveRadarrBlock::Downloads,
|
|
||||||
&None,
|
|
||||||
)
|
|
||||||
.handle();
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.data.radarr_data.main_tabs.get_active_route(),
|
app.data.radarr_data.main_tabs.get_active_route(),
|
||||||
&ActiveRadarrBlock::Collections.into()
|
&ActiveRadarrBlock::Collections.into()
|
||||||
@@ -114,6 +94,29 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_downloads_tab_right() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.radarr_data.main_tabs.set_index(2);
|
||||||
|
|
||||||
|
DownloadsHandler::with(
|
||||||
|
&DEFAULT_KEYBINDINGS.right.key,
|
||||||
|
&mut app,
|
||||||
|
&ActiveRadarrBlock::Downloads,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.radarr_data.main_tabs.get_active_route(),
|
||||||
|
&ActiveRadarrBlock::Blocklist.into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
&ActiveRadarrBlock::Blocklist.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_downloads_left_right_prompt_toggle(
|
fn test_downloads_left_right_prompt_toggle(
|
||||||
#[values(
|
#[values(
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_indexers_tab_left() {
|
fn test_indexers_tab_left() {
|
||||||
let mut app = App::default();
|
let mut app = App::default();
|
||||||
app.data.radarr_data.main_tabs.set_index(4);
|
app.data.radarr_data.main_tabs.set_index(5);
|
||||||
|
|
||||||
IndexersHandler::with(
|
IndexersHandler::with(
|
||||||
&DEFAULT_KEYBINDINGS.left.key,
|
&DEFAULT_KEYBINDINGS.left.key,
|
||||||
@@ -101,7 +101,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_indexers_tab_right() {
|
fn test_indexers_tab_right() {
|
||||||
let mut app = App::default();
|
let mut app = App::default();
|
||||||
app.data.radarr_data.main_tabs.set_index(4);
|
app.data.radarr_data.main_tabs.set_index(5);
|
||||||
|
|
||||||
IndexersHandler::with(
|
IndexersHandler::with(
|
||||||
&DEFAULT_KEYBINDINGS.right.key,
|
&DEFAULT_KEYBINDINGS.right.key,
|
||||||
|
|||||||
@@ -312,11 +312,11 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.data.radarr_data.main_tabs.get_active_route(),
|
app.data.radarr_data.main_tabs.get_active_route(),
|
||||||
&ActiveRadarrBlock::Downloads.into()
|
&ActiveRadarrBlock::Collections.into()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.get_current_route(),
|
app.get_current_route(),
|
||||||
&ActiveRadarrBlock::Downloads.into()
|
&ActiveRadarrBlock::Collections.into()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -776,6 +776,23 @@ mod tests {
|
|||||||
assert!(!app.data.radarr_data.prompt_confirm);
|
assert!(!app.data.radarr_data.prompt_confirm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_movies_sort_prompt_block_esc() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::MoviesSortPrompt.into());
|
||||||
|
|
||||||
|
LibraryHandler::with(
|
||||||
|
&ESC_KEY,
|
||||||
|
&mut app,
|
||||||
|
&ActiveRadarrBlock::MoviesSortPrompt,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_esc() {
|
fn test_default_esc() {
|
||||||
let mut app = App::default();
|
let mut app = App::default();
|
||||||
|
|||||||
@@ -298,6 +298,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
|
|||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
self.app.data.radarr_data.prompt_confirm = false;
|
self.app.data.radarr_data.prompt_confirm = false;
|
||||||
}
|
}
|
||||||
|
ActiveRadarrBlock::MoviesSortPrompt => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.app.data.radarr_data.movies.reset_search();
|
self.app.data.radarr_data.movies.reset_search();
|
||||||
self.app.data.radarr_data.movies.reset_filter();
|
self.app.data.radarr_data.movies.reset_filter();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::handlers::radarr_handlers::blocklist::BlocklistHandler;
|
||||||
use crate::handlers::radarr_handlers::collections::CollectionsHandler;
|
use crate::handlers::radarr_handlers::collections::CollectionsHandler;
|
||||||
use crate::handlers::radarr_handlers::downloads::DownloadsHandler;
|
use crate::handlers::radarr_handlers::downloads::DownloadsHandler;
|
||||||
use crate::handlers::radarr_handlers::indexers::IndexersHandler;
|
use crate::handlers::radarr_handlers::indexers::IndexersHandler;
|
||||||
@@ -9,6 +10,7 @@ use crate::handlers::KeyEventHandler;
|
|||||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||||
use crate::{App, Key};
|
use crate::{App, Key};
|
||||||
|
|
||||||
|
mod blocklist;
|
||||||
mod collections;
|
mod collections;
|
||||||
mod downloads;
|
mod downloads;
|
||||||
mod indexers;
|
mod indexers;
|
||||||
@@ -54,6 +56,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
|
|||||||
RootFoldersHandler::with(self.key, self.app, self.active_radarr_block, self.context)
|
RootFoldersHandler::with(self.key, self.app, self.active_radarr_block, self.context)
|
||||||
.handle()
|
.handle()
|
||||||
}
|
}
|
||||||
|
_ if BlocklistHandler::accepts(self.active_radarr_block) => {
|
||||||
|
BlocklistHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle()
|
||||||
|
}
|
||||||
_ => self.handle_key_event(),
|
_ => self.handle_key_event(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,13 @@ mod tests {
|
|||||||
use crate::test_handler_delegation;
|
use crate::test_handler_delegation;
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(0, ActiveRadarrBlock::System, ActiveRadarrBlock::Downloads)]
|
#[case(0, ActiveRadarrBlock::System, ActiveRadarrBlock::Collections)]
|
||||||
#[case(1, ActiveRadarrBlock::Movies, ActiveRadarrBlock::Collections)]
|
#[case(1, ActiveRadarrBlock::Movies, ActiveRadarrBlock::Downloads)]
|
||||||
#[case(2, ActiveRadarrBlock::Downloads, ActiveRadarrBlock::RootFolders)]
|
#[case(2, ActiveRadarrBlock::Collections, ActiveRadarrBlock::Blocklist)]
|
||||||
#[case(3, ActiveRadarrBlock::Collections, ActiveRadarrBlock::Indexers)]
|
#[case(3, ActiveRadarrBlock::Downloads, ActiveRadarrBlock::RootFolders)]
|
||||||
#[case(4, ActiveRadarrBlock::RootFolders, ActiveRadarrBlock::System)]
|
#[case(4, ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Indexers)]
|
||||||
#[case(5, ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Movies)]
|
#[case(5, ActiveRadarrBlock::RootFolders, ActiveRadarrBlock::System)]
|
||||||
|
#[case(6, ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Movies)]
|
||||||
fn test_radarr_handler_change_tab_left_right_keys(
|
fn test_radarr_handler_change_tab_left_right_keys(
|
||||||
#[case] index: usize,
|
#[case] index: usize,
|
||||||
#[case] left_block: ActiveRadarrBlock,
|
#[case] left_block: ActiveRadarrBlock,
|
||||||
@@ -68,6 +69,7 @@ mod tests {
|
|||||||
fn test_delegates_library_blocks_to_library_handler(
|
fn test_delegates_library_blocks_to_library_handler(
|
||||||
#[values(
|
#[values(
|
||||||
ActiveRadarrBlock::Movies,
|
ActiveRadarrBlock::Movies,
|
||||||
|
ActiveRadarrBlock::MoviesSortPrompt,
|
||||||
ActiveRadarrBlock::SearchMovie,
|
ActiveRadarrBlock::SearchMovie,
|
||||||
ActiveRadarrBlock::SearchMovieError,
|
ActiveRadarrBlock::SearchMovieError,
|
||||||
ActiveRadarrBlock::FilterMovies,
|
ActiveRadarrBlock::FilterMovies,
|
||||||
@@ -112,6 +114,7 @@ mod tests {
|
|||||||
#[values(
|
#[values(
|
||||||
ActiveRadarrBlock::Collections,
|
ActiveRadarrBlock::Collections,
|
||||||
ActiveRadarrBlock::SearchCollection,
|
ActiveRadarrBlock::SearchCollection,
|
||||||
|
ActiveRadarrBlock::CollectionsSortPrompt,
|
||||||
ActiveRadarrBlock::SearchCollectionError,
|
ActiveRadarrBlock::SearchCollectionError,
|
||||||
ActiveRadarrBlock::FilterCollections,
|
ActiveRadarrBlock::FilterCollections,
|
||||||
ActiveRadarrBlock::FilterCollectionsError,
|
ActiveRadarrBlock::FilterCollectionsError,
|
||||||
@@ -189,6 +192,24 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_delegates_blocklist_blocks_to_blocklist_handler(
|
||||||
|
#[values(
|
||||||
|
ActiveRadarrBlock::Blocklist,
|
||||||
|
ActiveRadarrBlock::BlocklistItemDetails,
|
||||||
|
ActiveRadarrBlock::DeleteBlocklistItemPrompt,
|
||||||
|
ActiveRadarrBlock::BlocklistClearAllItemsPrompt,
|
||||||
|
ActiveRadarrBlock::BlocklistSortPrompt
|
||||||
|
)]
|
||||||
|
active_radarr_block: ActiveRadarrBlock,
|
||||||
|
) {
|
||||||
|
test_handler_delegation!(
|
||||||
|
RadarrHandler,
|
||||||
|
ActiveRadarrBlock::Blocklist,
|
||||||
|
active_radarr_block
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_radarr_handler_accepts() {
|
fn test_radarr_handler_accepts() {
|
||||||
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
|
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_root_folders_tab_left() {
|
fn test_root_folders_tab_left() {
|
||||||
let mut app = App::default();
|
let mut app = App::default();
|
||||||
app.data.radarr_data.main_tabs.set_index(3);
|
app.data.radarr_data.main_tabs.set_index(4);
|
||||||
|
|
||||||
RootFoldersHandler::with(
|
RootFoldersHandler::with(
|
||||||
&DEFAULT_KEYBINDINGS.left.key,
|
&DEFAULT_KEYBINDINGS.left.key,
|
||||||
@@ -135,18 +135,18 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.data.radarr_data.main_tabs.get_active_route(),
|
app.data.radarr_data.main_tabs.get_active_route(),
|
||||||
&ActiveRadarrBlock::Collections.into()
|
&ActiveRadarrBlock::Blocklist.into()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.get_current_route(),
|
app.get_current_route(),
|
||||||
&ActiveRadarrBlock::Collections.into()
|
&ActiveRadarrBlock::Blocklist.into()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_root_folders_tab_right() {
|
fn test_root_folders_tab_right() {
|
||||||
let mut app = App::default();
|
let mut app = App::default();
|
||||||
app.data.radarr_data.main_tabs.set_index(3);
|
app.data.radarr_data.main_tabs.set_index(4);
|
||||||
|
|
||||||
RootFoldersHandler::with(
|
RootFoldersHandler::with(
|
||||||
&DEFAULT_KEYBINDINGS.right.key,
|
&DEFAULT_KEYBINDINGS.right.key,
|
||||||
|
|||||||
@@ -422,11 +422,6 @@ mod tests {
|
|||||||
let mut app = App::default();
|
let mut app = App::default();
|
||||||
app.push_navigation_stack(ActiveRadarrBlock::System.into());
|
app.push_navigation_stack(ActiveRadarrBlock::System.into());
|
||||||
app.push_navigation_stack(ActiveRadarrBlock::SystemUpdates.into());
|
app.push_navigation_stack(ActiveRadarrBlock::SystemUpdates.into());
|
||||||
app
|
|
||||||
.data
|
|
||||||
.radarr_data
|
|
||||||
.queued_events
|
|
||||||
.set_items(vec![QueueEvent::default()]);
|
|
||||||
|
|
||||||
SystemDetailsHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::SystemUpdates, &None)
|
SystemDetailsHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::SystemUpdates, &None)
|
||||||
.handle();
|
.handle();
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_system_tab_left() {
|
fn test_system_tab_left() {
|
||||||
let mut app = App::default();
|
let mut app = App::default();
|
||||||
app.data.radarr_data.main_tabs.set_index(5);
|
app.data.radarr_data.main_tabs.set_index(6);
|
||||||
|
|
||||||
SystemHandler::with(
|
SystemHandler::with(
|
||||||
&DEFAULT_KEYBINDINGS.left.key,
|
&DEFAULT_KEYBINDINGS.left.key,
|
||||||
@@ -42,7 +42,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_system_tab_right() {
|
fn test_system_tab_right() {
|
||||||
let mut app = App::default();
|
let mut app = App::default();
|
||||||
app.data.radarr_data.main_tabs.set_index(5);
|
app.data.radarr_data.main_tabs.set_index(6);
|
||||||
|
|
||||||
SystemHandler::with(
|
SystemHandler::with(
|
||||||
&DEFAULT_KEYBINDINGS.right.key,
|
&DEFAULT_KEYBINDINGS.right.key,
|
||||||
|
|||||||
@@ -54,6 +54,29 @@ pub struct AddRootFolderBody {
|
|||||||
pub path: String,
|
pub path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct BlocklistResponse {
|
||||||
|
pub records: Vec<BlocklistItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, 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 movie_id: i64,
|
||||||
|
pub source_title: String,
|
||||||
|
pub languages: Vec<Language>,
|
||||||
|
pub quality: QualityWrapper,
|
||||||
|
pub custom_formats: Option<Vec<Language>>,
|
||||||
|
pub date: DateTime<Utc>,
|
||||||
|
pub protocol: String,
|
||||||
|
pub indexer: String,
|
||||||
|
pub message: String,
|
||||||
|
pub movie: Movie,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Derivative, Default, Clone, Debug, PartialEq, Eq)]
|
#[derive(Deserialize, Derivative, Default, Clone, Debug, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Collection {
|
pub struct Collection {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
use crate::app::context_clues::build_context_clue_string;
|
use crate::app::context_clues::build_context_clue_string;
|
||||||
use crate::app::radarr::radarr_context_clues::{
|
use crate::app::radarr::radarr_context_clues::{
|
||||||
COLLECTIONS_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
BLOCKLIST_CONTEXT_CLUES, COLLECTIONS_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES,
|
||||||
LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
|
INDEXERS_CONTEXT_CLUES, LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
|
||||||
MANUAL_MOVIE_SEARCH_CONTEXT_CLUES, MOVIE_DETAILS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES,
|
MANUAL_MOVIE_SEARCH_CONTEXT_CLUES, MOVIE_DETAILS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES,
|
||||||
SYSTEM_CONTEXT_CLUES,
|
SYSTEM_CONTEXT_CLUES,
|
||||||
};
|
};
|
||||||
use crate::models::radarr_models::{
|
use crate::models::radarr_models::{
|
||||||
AddMovieSearchResult, Collection, CollectionMovie, DiskSpace, DownloadRecord, Indexer,
|
AddMovieSearchResult, BlocklistItem, Collection, CollectionMovie, DiskSpace, DownloadRecord,
|
||||||
IndexerSettings, Movie, QueueEvent, RootFolder, Task,
|
Indexer, IndexerSettings, Movie, QueueEvent, RootFolder, Task,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_data::radarr::modals::{
|
use crate::models::servarr_data::radarr::modals::{
|
||||||
AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem,
|
AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem,
|
||||||
@@ -40,6 +40,7 @@ pub struct RadarrData<'a> {
|
|||||||
pub selected_block: BlockSelectionState<'a, ActiveRadarrBlock>,
|
pub selected_block: BlockSelectionState<'a, ActiveRadarrBlock>,
|
||||||
pub downloads: StatefulTable<DownloadRecord>,
|
pub downloads: StatefulTable<DownloadRecord>,
|
||||||
pub indexers: StatefulTable<Indexer>,
|
pub indexers: StatefulTable<Indexer>,
|
||||||
|
pub blocklist: StatefulTable<BlocklistItem>,
|
||||||
pub quality_profile_map: BiMap<i64, String>,
|
pub quality_profile_map: BiMap<i64, String>,
|
||||||
pub tags_map: BiMap<i64, String>,
|
pub tags_map: BiMap<i64, String>,
|
||||||
pub collections: StatefulTable<Collection>,
|
pub collections: StatefulTable<Collection>,
|
||||||
@@ -91,6 +92,7 @@ impl<'a> Default for RadarrData<'a> {
|
|||||||
selected_block: BlockSelectionState::default(),
|
selected_block: BlockSelectionState::default(),
|
||||||
downloads: StatefulTable::default(),
|
downloads: StatefulTable::default(),
|
||||||
indexers: StatefulTable::default(),
|
indexers: StatefulTable::default(),
|
||||||
|
blocklist: StatefulTable::default(),
|
||||||
quality_profile_map: BiMap::default(),
|
quality_profile_map: BiMap::default(),
|
||||||
tags_map: BiMap::default(),
|
tags_map: BiMap::default(),
|
||||||
collections: StatefulTable::default(),
|
collections: StatefulTable::default(),
|
||||||
@@ -122,6 +124,12 @@ impl<'a> Default for RadarrData<'a> {
|
|||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&LIBRARY_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&LIBRARY_CONTEXT_CLUES)),
|
||||||
},
|
},
|
||||||
|
TabRoute {
|
||||||
|
title: "Collections",
|
||||||
|
route: ActiveRadarrBlock::Collections.into(),
|
||||||
|
help: String::new(),
|
||||||
|
contextual_help: Some(build_context_clue_string(&COLLECTIONS_CONTEXT_CLUES)),
|
||||||
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Downloads",
|
title: "Downloads",
|
||||||
route: ActiveRadarrBlock::Downloads.into(),
|
route: ActiveRadarrBlock::Downloads.into(),
|
||||||
@@ -129,10 +137,10 @@ impl<'a> Default for RadarrData<'a> {
|
|||||||
contextual_help: Some(build_context_clue_string(&DOWNLOADS_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&DOWNLOADS_CONTEXT_CLUES)),
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Collections",
|
title: "Blocklist",
|
||||||
route: ActiveRadarrBlock::Collections.into(),
|
route: ActiveRadarrBlock::Blocklist.into(),
|
||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&COLLECTIONS_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&BLOCKLIST_CONTEXT_CLUES)),
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Root Folders",
|
title: "Root Folders",
|
||||||
@@ -213,11 +221,16 @@ pub enum ActiveRadarrBlock {
|
|||||||
AddMovieEmptySearchResults,
|
AddMovieEmptySearchResults,
|
||||||
AddRootFolderPrompt,
|
AddRootFolderPrompt,
|
||||||
AutomaticallySearchMoviePrompt,
|
AutomaticallySearchMoviePrompt,
|
||||||
|
Blocklist,
|
||||||
|
BlocklistClearAllItemsPrompt,
|
||||||
|
BlocklistItemDetails,
|
||||||
|
BlocklistSortPrompt,
|
||||||
Collections,
|
Collections,
|
||||||
CollectionsSortPrompt,
|
CollectionsSortPrompt,
|
||||||
CollectionDetails,
|
CollectionDetails,
|
||||||
Cast,
|
Cast,
|
||||||
Crew,
|
Crew,
|
||||||
|
DeleteBlocklistItemPrompt,
|
||||||
DeleteDownloadPrompt,
|
DeleteDownloadPrompt,
|
||||||
DeleteIndexerPrompt,
|
DeleteIndexerPrompt,
|
||||||
DeleteMoviePrompt,
|
DeleteMoviePrompt,
|
||||||
@@ -323,6 +336,13 @@ pub static ROOT_FOLDERS_BLOCKS: [ActiveRadarrBlock; 3] = [
|
|||||||
ActiveRadarrBlock::AddRootFolderPrompt,
|
ActiveRadarrBlock::AddRootFolderPrompt,
|
||||||
ActiveRadarrBlock::DeleteRootFolderPrompt,
|
ActiveRadarrBlock::DeleteRootFolderPrompt,
|
||||||
];
|
];
|
||||||
|
pub static BLOCKLIST_BLOCKS: [ActiveRadarrBlock; 5] = [
|
||||||
|
ActiveRadarrBlock::Blocklist,
|
||||||
|
ActiveRadarrBlock::BlocklistItemDetails,
|
||||||
|
ActiveRadarrBlock::DeleteBlocklistItemPrompt,
|
||||||
|
ActiveRadarrBlock::BlocklistClearAllItemsPrompt,
|
||||||
|
ActiveRadarrBlock::BlocklistSortPrompt,
|
||||||
|
];
|
||||||
pub static ADD_MOVIE_BLOCKS: [ActiveRadarrBlock; 10] = [
|
pub static ADD_MOVIE_BLOCKS: [ActiveRadarrBlock; 10] = [
|
||||||
ActiveRadarrBlock::AddMovieSearchInput,
|
ActiveRadarrBlock::AddMovieSearchInput,
|
||||||
ActiveRadarrBlock::AddMovieSearchResults,
|
ActiveRadarrBlock::AddMovieSearchResults,
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ mod tests {
|
|||||||
|
|
||||||
use crate::app::context_clues::build_context_clue_string;
|
use crate::app::context_clues::build_context_clue_string;
|
||||||
use crate::app::radarr::radarr_context_clues::{
|
use crate::app::radarr::radarr_context_clues::{
|
||||||
COLLECTIONS_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
BLOCKLIST_CONTEXT_CLUES, COLLECTIONS_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES,
|
||||||
LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
|
INDEXERS_CONTEXT_CLUES, LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
|
||||||
MANUAL_MOVIE_SEARCH_CONTEXT_CLUES, MOVIE_DETAILS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES,
|
MANUAL_MOVIE_SEARCH_CONTEXT_CLUES, MOVIE_DETAILS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES,
|
||||||
SYSTEM_CONTEXT_CLUES,
|
SYSTEM_CONTEXT_CLUES,
|
||||||
};
|
};
|
||||||
@@ -64,6 +64,7 @@ mod tests {
|
|||||||
assert_eq!(radarr_data.selected_block, BlockSelectionState::default());
|
assert_eq!(radarr_data.selected_block, BlockSelectionState::default());
|
||||||
assert!(radarr_data.downloads.items.is_empty());
|
assert!(radarr_data.downloads.items.is_empty());
|
||||||
assert!(radarr_data.indexers.items.is_empty());
|
assert!(radarr_data.indexers.items.is_empty());
|
||||||
|
assert!(radarr_data.blocklist.items.is_empty());
|
||||||
assert!(radarr_data.quality_profile_map.is_empty());
|
assert!(radarr_data.quality_profile_map.is_empty());
|
||||||
assert!(radarr_data.tags_map.is_empty());
|
assert!(radarr_data.tags_map.is_empty());
|
||||||
assert!(radarr_data.collections.items.is_empty());
|
assert!(radarr_data.collections.items.is_empty());
|
||||||
@@ -89,7 +90,7 @@ mod tests {
|
|||||||
assert!(!radarr_data.delete_movie_files);
|
assert!(!radarr_data.delete_movie_files);
|
||||||
assert!(!radarr_data.add_list_exclusion);
|
assert!(!radarr_data.add_list_exclusion);
|
||||||
|
|
||||||
assert_eq!(radarr_data.main_tabs.tabs.len(), 6);
|
assert_eq!(radarr_data.main_tabs.tabs.len(), 7);
|
||||||
|
|
||||||
assert_str_eq!(radarr_data.main_tabs.tabs[0].title, "Library");
|
assert_str_eq!(radarr_data.main_tabs.tabs[0].title, "Library");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -102,58 +103,69 @@ mod tests {
|
|||||||
Some(build_context_clue_string(&LIBRARY_CONTEXT_CLUES))
|
Some(build_context_clue_string(&LIBRARY_CONTEXT_CLUES))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_str_eq!(radarr_data.main_tabs.tabs[1].title, "Downloads");
|
assert_str_eq!(radarr_data.main_tabs.tabs[1].title, "Collections");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
radarr_data.main_tabs.tabs[1].route,
|
radarr_data.main_tabs.tabs[1].route,
|
||||||
ActiveRadarrBlock::Downloads.into()
|
ActiveRadarrBlock::Collections.into()
|
||||||
);
|
);
|
||||||
assert!(radarr_data.main_tabs.tabs[1].help.is_empty());
|
assert!(radarr_data.main_tabs.tabs[1].help.is_empty());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
radarr_data.main_tabs.tabs[1].contextual_help,
|
radarr_data.main_tabs.tabs[1].contextual_help,
|
||||||
Some(build_context_clue_string(&DOWNLOADS_CONTEXT_CLUES))
|
Some(build_context_clue_string(&COLLECTIONS_CONTEXT_CLUES))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_str_eq!(radarr_data.main_tabs.tabs[2].title, "Collections");
|
assert_str_eq!(radarr_data.main_tabs.tabs[2].title, "Downloads");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
radarr_data.main_tabs.tabs[2].route,
|
radarr_data.main_tabs.tabs[2].route,
|
||||||
ActiveRadarrBlock::Collections.into()
|
ActiveRadarrBlock::Downloads.into()
|
||||||
);
|
);
|
||||||
assert!(radarr_data.main_tabs.tabs[2].help.is_empty());
|
assert!(radarr_data.main_tabs.tabs[2].help.is_empty());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
radarr_data.main_tabs.tabs[2].contextual_help,
|
radarr_data.main_tabs.tabs[2].contextual_help,
|
||||||
Some(build_context_clue_string(&COLLECTIONS_CONTEXT_CLUES))
|
Some(build_context_clue_string(&DOWNLOADS_CONTEXT_CLUES))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_str_eq!(radarr_data.main_tabs.tabs[3].title, "Root Folders");
|
assert_str_eq!(radarr_data.main_tabs.tabs[3].title, "Blocklist");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
radarr_data.main_tabs.tabs[3].route,
|
radarr_data.main_tabs.tabs[3].route,
|
||||||
ActiveRadarrBlock::RootFolders.into()
|
ActiveRadarrBlock::Blocklist.into()
|
||||||
);
|
);
|
||||||
assert!(radarr_data.main_tabs.tabs[3].help.is_empty());
|
assert!(radarr_data.main_tabs.tabs[3].help.is_empty());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
radarr_data.main_tabs.tabs[3].contextual_help,
|
radarr_data.main_tabs.tabs[3].contextual_help,
|
||||||
Some(build_context_clue_string(&ROOT_FOLDERS_CONTEXT_CLUES))
|
Some(build_context_clue_string(&BLOCKLIST_CONTEXT_CLUES))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_str_eq!(radarr_data.main_tabs.tabs[4].title, "Indexers");
|
assert_str_eq!(radarr_data.main_tabs.tabs[4].title, "Root Folders");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
radarr_data.main_tabs.tabs[4].route,
|
radarr_data.main_tabs.tabs[4].route,
|
||||||
ActiveRadarrBlock::Indexers.into()
|
ActiveRadarrBlock::RootFolders.into()
|
||||||
);
|
);
|
||||||
assert!(radarr_data.main_tabs.tabs[4].help.is_empty());
|
assert!(radarr_data.main_tabs.tabs[4].help.is_empty());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
radarr_data.main_tabs.tabs[4].contextual_help,
|
radarr_data.main_tabs.tabs[4].contextual_help,
|
||||||
Some(build_context_clue_string(&INDEXERS_CONTEXT_CLUES))
|
Some(build_context_clue_string(&ROOT_FOLDERS_CONTEXT_CLUES))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_str_eq!(radarr_data.main_tabs.tabs[5].title, "System");
|
assert_str_eq!(radarr_data.main_tabs.tabs[5].title, "Indexers");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
radarr_data.main_tabs.tabs[5].route,
|
radarr_data.main_tabs.tabs[5].route,
|
||||||
ActiveRadarrBlock::System.into()
|
ActiveRadarrBlock::Indexers.into()
|
||||||
);
|
);
|
||||||
assert!(radarr_data.main_tabs.tabs[5].help.is_empty());
|
assert!(radarr_data.main_tabs.tabs[5].help.is_empty());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
radarr_data.main_tabs.tabs[5].contextual_help,
|
radarr_data.main_tabs.tabs[5].contextual_help,
|
||||||
|
Some(build_context_clue_string(&INDEXERS_CONTEXT_CLUES))
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_str_eq!(radarr_data.main_tabs.tabs[6].title, "System");
|
||||||
|
assert_eq!(
|
||||||
|
radarr_data.main_tabs.tabs[6].route,
|
||||||
|
ActiveRadarrBlock::System.into()
|
||||||
|
);
|
||||||
|
assert!(radarr_data.main_tabs.tabs[6].help.is_empty());
|
||||||
|
assert_eq!(
|
||||||
|
radarr_data.main_tabs.tabs[6].contextual_help,
|
||||||
Some(build_context_clue_string(&SYSTEM_CONTEXT_CLUES))
|
Some(build_context_clue_string(&SYSTEM_CONTEXT_CLUES))
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -246,10 +258,10 @@ mod tests {
|
|||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use crate::models::servarr_data::radarr::radarr_data::{
|
use crate::models::servarr_data::radarr::radarr_data::{
|
||||||
ActiveRadarrBlock, ADD_MOVIE_BLOCKS, ADD_MOVIE_SELECTION_BLOCKS, COLLECTIONS_BLOCKS,
|
ActiveRadarrBlock, ADD_MOVIE_BLOCKS, ADD_MOVIE_SELECTION_BLOCKS, BLOCKLIST_BLOCKS,
|
||||||
COLLECTION_DETAILS_BLOCKS, DELETE_MOVIE_BLOCKS, DELETE_MOVIE_SELECTION_BLOCKS,
|
COLLECTIONS_BLOCKS, COLLECTION_DETAILS_BLOCKS, DELETE_MOVIE_BLOCKS,
|
||||||
DOWNLOADS_BLOCKS, EDIT_COLLECTION_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS,
|
DELETE_MOVIE_SELECTION_BLOCKS, DOWNLOADS_BLOCKS, EDIT_COLLECTION_BLOCKS,
|
||||||
EDIT_INDEXER_BLOCKS, EDIT_INDEXER_NZB_SELECTION_BLOCKS,
|
EDIT_COLLECTION_SELECTION_BLOCKS, EDIT_INDEXER_BLOCKS, EDIT_INDEXER_NZB_SELECTION_BLOCKS,
|
||||||
EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, EDIT_MOVIE_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS,
|
EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, EDIT_MOVIE_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS,
|
||||||
INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS, LIBRARY_BLOCKS,
|
INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS, LIBRARY_BLOCKS,
|
||||||
MOVIE_DETAILS_BLOCKS, ROOT_FOLDERS_BLOCKS, SYSTEM_DETAILS_BLOCKS,
|
MOVIE_DETAILS_BLOCKS, ROOT_FOLDERS_BLOCKS, SYSTEM_DETAILS_BLOCKS,
|
||||||
@@ -296,6 +308,16 @@ mod tests {
|
|||||||
assert!(ROOT_FOLDERS_BLOCKS.contains(&ActiveRadarrBlock::DeleteRootFolderPrompt));
|
assert!(ROOT_FOLDERS_BLOCKS.contains(&ActiveRadarrBlock::DeleteRootFolderPrompt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_blocks_contents() {
|
||||||
|
assert_eq!(BLOCKLIST_BLOCKS.len(), 5);
|
||||||
|
assert!(BLOCKLIST_BLOCKS.contains(&ActiveRadarrBlock::Blocklist));
|
||||||
|
assert!(BLOCKLIST_BLOCKS.contains(&ActiveRadarrBlock::BlocklistItemDetails));
|
||||||
|
assert!(BLOCKLIST_BLOCKS.contains(&ActiveRadarrBlock::DeleteBlocklistItemPrompt));
|
||||||
|
assert!(BLOCKLIST_BLOCKS.contains(&ActiveRadarrBlock::BlocklistClearAllItemsPrompt));
|
||||||
|
assert!(BLOCKLIST_BLOCKS.contains(&ActiveRadarrBlock::BlocklistSortPrompt));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_movie_blocks_contents() {
|
fn test_add_movie_blocks_contents() {
|
||||||
assert_eq!(ADD_MOVIE_BLOCKS.len(), 10);
|
assert_eq!(ADD_MOVIE_BLOCKS.len(), 10);
|
||||||
|
|||||||
@@ -143,7 +143,11 @@ impl<'a, 'b> Network<'a, 'b> {
|
|||||||
.put(uri)
|
.put(uri)
|
||||||
.json(&body.unwrap_or_default())
|
.json(&body.unwrap_or_default())
|
||||||
.header("X-Api-Key", api_token),
|
.header("X-Api-Key", api_token),
|
||||||
RequestMethod::Delete => self.client.delete(uri).header("X-Api-Key", api_token),
|
RequestMethod::Delete => self
|
||||||
|
.client
|
||||||
|
.delete(uri)
|
||||||
|
.json(&body.unwrap_or_default())
|
||||||
|
.header("X-Api-Key", api_token),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ use urlencoding::encode;
|
|||||||
|
|
||||||
use crate::app::RadarrConfig;
|
use crate::app::RadarrConfig;
|
||||||
use crate::models::radarr_models::{
|
use crate::models::radarr_models::{
|
||||||
AddMovieBody, AddMovieSearchResult, AddOptions, AddRootFolderBody, Collection, CollectionMovie,
|
AddMovieBody, AddMovieSearchResult, AddOptions, AddRootFolderBody, BlocklistResponse, Collection,
|
||||||
CommandBody, Credit, CreditType, DiskSpace, DownloadRecord, DownloadsResponse, Indexer,
|
CollectionMovie, CommandBody, Credit, CreditType, DiskSpace, DownloadRecord, DownloadsResponse,
|
||||||
IndexerSettings, IndexerTestResult, LogResponse, Movie, MovieCommandBody, MovieHistoryItem,
|
Indexer, IndexerSettings, IndexerTestResult, LogResponse, Movie, MovieCommandBody,
|
||||||
QualityProfile, QueueEvent, Release, ReleaseDownloadBody, RootFolder, SystemStatus, Tag, Task,
|
MovieHistoryItem, QualityProfile, QueueEvent, Release, ReleaseDownloadBody, RootFolder,
|
||||||
Update,
|
SystemStatus, Tag, Task, Update,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_data::radarr::modals::{
|
use crate::models::servarr_data::radarr::modals::{
|
||||||
AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem,
|
AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem,
|
||||||
@@ -33,6 +33,8 @@ mod radarr_network_tests;
|
|||||||
pub enum RadarrEvent {
|
pub enum RadarrEvent {
|
||||||
AddMovie,
|
AddMovie,
|
||||||
AddRootFolder,
|
AddRootFolder,
|
||||||
|
ClearBlocklist,
|
||||||
|
DeleteBlocklistItem,
|
||||||
DeleteDownload,
|
DeleteDownload,
|
||||||
DeleteIndexer,
|
DeleteIndexer,
|
||||||
DeleteMovie,
|
DeleteMovie,
|
||||||
@@ -42,6 +44,7 @@ pub enum RadarrEvent {
|
|||||||
EditCollection,
|
EditCollection,
|
||||||
EditIndexer,
|
EditIndexer,
|
||||||
EditMovie,
|
EditMovie,
|
||||||
|
GetBlocklist,
|
||||||
GetCollections,
|
GetCollections,
|
||||||
GetDownloads,
|
GetDownloads,
|
||||||
GetIndexers,
|
GetIndexers,
|
||||||
@@ -75,6 +78,9 @@ pub enum RadarrEvent {
|
|||||||
impl RadarrEvent {
|
impl RadarrEvent {
|
||||||
const fn resource(self) -> &'static str {
|
const fn resource(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
|
RadarrEvent::ClearBlocklist => "/blocklist/bulk",
|
||||||
|
RadarrEvent::DeleteBlocklistItem => "/blocklist",
|
||||||
|
RadarrEvent::GetBlocklist => "/blocklist?page=1&pageSize=10000",
|
||||||
RadarrEvent::GetCollections | RadarrEvent::EditCollection => "/collection",
|
RadarrEvent::GetCollections | RadarrEvent::EditCollection => "/collection",
|
||||||
RadarrEvent::GetDownloads | RadarrEvent::DeleteDownload => "/queue",
|
RadarrEvent::GetDownloads | RadarrEvent::DeleteDownload => "/queue",
|
||||||
RadarrEvent::GetIndexers | RadarrEvent::EditIndexer | RadarrEvent::DeleteIndexer => {
|
RadarrEvent::GetIndexers | RadarrEvent::EditIndexer | RadarrEvent::DeleteIndexer => {
|
||||||
@@ -125,6 +131,8 @@ impl<'a, 'b> Network<'a, 'b> {
|
|||||||
match radarr_event {
|
match radarr_event {
|
||||||
RadarrEvent::AddMovie => self.add_movie().await,
|
RadarrEvent::AddMovie => self.add_movie().await,
|
||||||
RadarrEvent::AddRootFolder => self.add_root_folder().await,
|
RadarrEvent::AddRootFolder => self.add_root_folder().await,
|
||||||
|
RadarrEvent::ClearBlocklist => self.clear_blocklist().await,
|
||||||
|
RadarrEvent::DeleteBlocklistItem => self.delete_blocklist_item().await,
|
||||||
RadarrEvent::DeleteDownload => self.delete_download().await,
|
RadarrEvent::DeleteDownload => self.delete_download().await,
|
||||||
RadarrEvent::DeleteIndexer => self.delete_indexer().await,
|
RadarrEvent::DeleteIndexer => self.delete_indexer().await,
|
||||||
RadarrEvent::DeleteMovie => self.delete_movie().await,
|
RadarrEvent::DeleteMovie => self.delete_movie().await,
|
||||||
@@ -134,6 +142,7 @@ impl<'a, 'b> Network<'a, 'b> {
|
|||||||
RadarrEvent::EditCollection => self.edit_collection().await,
|
RadarrEvent::EditCollection => self.edit_collection().await,
|
||||||
RadarrEvent::EditIndexer => self.edit_indexer().await,
|
RadarrEvent::EditIndexer => self.edit_indexer().await,
|
||||||
RadarrEvent::EditMovie => self.edit_movie().await,
|
RadarrEvent::EditMovie => self.edit_movie().await,
|
||||||
|
RadarrEvent::GetBlocklist => self.get_blocklist().await,
|
||||||
RadarrEvent::GetCollections => self.get_collections().await,
|
RadarrEvent::GetCollections => self.get_collections().await,
|
||||||
RadarrEvent::GetDownloads => self.get_downloads().await,
|
RadarrEvent::GetDownloads => self.get_downloads().await,
|
||||||
RadarrEvent::GetIndexers => self.get_indexers().await,
|
RadarrEvent::GetIndexers => self.get_indexers().await,
|
||||||
@@ -319,6 +328,64 @@ impl<'a, 'b> Network<'a, 'b> {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn clear_blocklist(&mut self) {
|
||||||
|
info!("Clearing Radarr blocklist");
|
||||||
|
|
||||||
|
let ids = self
|
||||||
|
.app
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.map(|item| item.id)
|
||||||
|
.collect::<Vec<i64>>();
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.radarr_request_props_from(
|
||||||
|
RadarrEvent::ClearBlocklist.resource(),
|
||||||
|
RequestMethod::Delete,
|
||||||
|
Some(json!({"ids": ids})),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<Value, ()>(request_props, |_, _| ())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_blocklist_item(&mut self) {
|
||||||
|
let blocklist_item_id = self
|
||||||
|
.app
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.current_selection()
|
||||||
|
.id;
|
||||||
|
|
||||||
|
info!("Deleting Radarr blocklist item for item with id: {blocklist_item_id}");
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.radarr_request_props_from(
|
||||||
|
format!(
|
||||||
|
"{}/{blocklist_item_id}",
|
||||||
|
RadarrEvent::DeleteBlocklistItem.resource()
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
RequestMethod::Delete,
|
||||||
|
None::<()>,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), ()>(request_props, |_, _| ())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
async fn delete_download(&mut self) {
|
async fn delete_download(&mut self) {
|
||||||
let download_id = self
|
let download_id = self
|
||||||
.app
|
.app
|
||||||
@@ -794,6 +861,32 @@ impl<'a, 'b> Network<'a, 'b> {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_blocklist(&mut self) {
|
||||||
|
info!("Fetching blocklist");
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.radarr_request_props_from(
|
||||||
|
RadarrEvent::GetBlocklist.resource(),
|
||||||
|
RequestMethod::Get,
|
||||||
|
None::<()>,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), BlocklistResponse>(request_props, |blocklist_resp, mut app| {
|
||||||
|
if !matches!(
|
||||||
|
app.get_current_route(),
|
||||||
|
Route::Radarr(ActiveRadarrBlock::BlocklistSortPrompt, _)
|
||||||
|
) {
|
||||||
|
let mut blocklist_vec = blocklist_resp.records;
|
||||||
|
blocklist_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
||||||
|
app.data.radarr_data.blocklist.set_items(blocklist_vec);
|
||||||
|
app.data.radarr_data.blocklist.apply_sorting_toggle(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_collections(&mut self) {
|
async fn get_collections(&mut self) {
|
||||||
info!("Fetching Radarr collections");
|
info!("Fetching Radarr collections");
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ mod test {
|
|||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use crate::models::radarr_models::{
|
use crate::models::radarr_models::{
|
||||||
CollectionMovie, IndexerField, Language, MediaInfo, MinimumAvailability, Monitor, MovieFile,
|
BlocklistItem, CollectionMovie, IndexerField, Language, MediaInfo, MinimumAvailability,
|
||||||
Quality, QualityWrapper, Rating, RatingsList,
|
Monitor, MovieFile, Quality, QualityWrapper, Rating, RatingsList,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||||
use crate::models::stateful_table::SortOption;
|
use crate::models::stateful_table::SortOption;
|
||||||
@@ -186,6 +186,9 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
|
#[case(RadarrEvent::ClearBlocklist, "/blocklist/bulk")]
|
||||||
|
#[case(RadarrEvent::DeleteBlocklistItem, "/blocklist")]
|
||||||
|
#[case(RadarrEvent::GetBlocklist, "/blocklist?page=1&pageSize=10000")]
|
||||||
#[case(RadarrEvent::GetLogs, "/log")]
|
#[case(RadarrEvent::GetLogs, "/log")]
|
||||||
#[case(RadarrEvent::SearchNewMovie, "/movie/lookup")]
|
#[case(RadarrEvent::SearchNewMovie, "/movie/lookup")]
|
||||||
#[case(RadarrEvent::GetMovieCredits, "/credit")]
|
#[case(RadarrEvent::GetMovieCredits, "/credit")]
|
||||||
@@ -1302,6 +1305,271 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_blocklist_event(#[values(true, false)] use_custom_sorting: bool) {
|
||||||
|
let blocklist_json = json!({"records": [{
|
||||||
|
"id": 123,
|
||||||
|
"movieId": 1007,
|
||||||
|
"sourceTitle": "z movie",
|
||||||
|
"languages": [{"name": "English"}],
|
||||||
|
"quality": {"quality": {"name": "HD - 1080p"}},
|
||||||
|
"customFormats": [{"name": "English"}],
|
||||||
|
"date": "2024-02-10T07:28:45Z",
|
||||||
|
"protocol": "usenet",
|
||||||
|
"indexer": "DrunkenSlug (Prowlarr)",
|
||||||
|
"message": "test message",
|
||||||
|
"movie": {
|
||||||
|
"id": 1007,
|
||||||
|
"title": "z movie",
|
||||||
|
"tmdbId": 1234,
|
||||||
|
"originalLanguage": {"name": "English"},
|
||||||
|
"sizeOnDisk": 3543348019i64,
|
||||||
|
"status": "Downloaded",
|
||||||
|
"overview": "Blah blah blah",
|
||||||
|
"path": "/nfs/movies",
|
||||||
|
"studio": "21st Century Alex",
|
||||||
|
"genres": ["cool", "family", "fun"],
|
||||||
|
"year": 2023,
|
||||||
|
"monitored": true,
|
||||||
|
"hasFile": true,
|
||||||
|
"runtime": 120,
|
||||||
|
"qualityProfileId": 2222,
|
||||||
|
"minimumAvailability": "announced",
|
||||||
|
"certification": "R",
|
||||||
|
"tags": [1],
|
||||||
|
"ratings": {
|
||||||
|
"imdb": {"value": 9.9},
|
||||||
|
"tmdb": {"value": 9.9},
|
||||||
|
"rottenTomatoes": {"value": 9.9}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
"id": 456,
|
||||||
|
"movieId": 2001,
|
||||||
|
"sourceTitle": "A Movie",
|
||||||
|
"languages": [{"name": "English"}],
|
||||||
|
"quality": {"quality": {"name": "HD - 1080p"}},
|
||||||
|
"customFormats": [{"name": "English"}],
|
||||||
|
"date": "2024-02-10T07:28:45Z",
|
||||||
|
"protocol": "usenet",
|
||||||
|
"indexer": "DrunkenSlug (Prowlarr)",
|
||||||
|
"message": "test message",
|
||||||
|
"movie": {
|
||||||
|
"id": 2001,
|
||||||
|
"title": "A Movie",
|
||||||
|
"tmdbId": 1234,
|
||||||
|
"originalLanguage": {"name": "English"},
|
||||||
|
"sizeOnDisk": 3543348019i64,
|
||||||
|
"status": "Downloaded",
|
||||||
|
"overview": "Blah blah blah",
|
||||||
|
"path": "/nfs/movies",
|
||||||
|
"studio": "21st Century Alex",
|
||||||
|
"genres": ["cool", "family", "fun"],
|
||||||
|
"year": 2023,
|
||||||
|
"monitored": true,
|
||||||
|
"hasFile": true,
|
||||||
|
"runtime": 120,
|
||||||
|
"qualityProfileId": 2222,
|
||||||
|
"minimumAvailability": "announced",
|
||||||
|
"certification": "R",
|
||||||
|
"tags": [1],
|
||||||
|
"ratings": {
|
||||||
|
"imdb": {"value": 9.9},
|
||||||
|
"tmdb": {"value": 9.9},
|
||||||
|
"rottenTomatoes": {"value": 9.9}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}]});
|
||||||
|
let mut expected_blocklist = vec![
|
||||||
|
BlocklistItem {
|
||||||
|
id: 123,
|
||||||
|
movie_id: 1007,
|
||||||
|
source_title: "z movie".into(),
|
||||||
|
movie: Movie {
|
||||||
|
id: 1007,
|
||||||
|
title: "z movie".into(),
|
||||||
|
movie_file: None,
|
||||||
|
collection: None,
|
||||||
|
..movie()
|
||||||
|
},
|
||||||
|
..blocklist_item()
|
||||||
|
},
|
||||||
|
BlocklistItem {
|
||||||
|
id: 456,
|
||||||
|
movie_id: 2001,
|
||||||
|
source_title: "A Movie".into(),
|
||||||
|
movie: Movie {
|
||||||
|
id: 2001,
|
||||||
|
title: "A Movie".into(),
|
||||||
|
movie_file: None,
|
||||||
|
collection: None,
|
||||||
|
..movie()
|
||||||
|
},
|
||||||
|
..blocklist_item()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let (async_server, app_arc, _server) = mock_radarr_api(
|
||||||
|
RequestMethod::Get,
|
||||||
|
None,
|
||||||
|
Some(blocklist_json),
|
||||||
|
None,
|
||||||
|
RadarrEvent::GetBlocklist.resource(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
app_arc.lock().await.data.radarr_data.blocklist.sort_asc = true;
|
||||||
|
if use_custom_sorting {
|
||||||
|
let cmp_fn = |a: &BlocklistItem, b: &BlocklistItem| {
|
||||||
|
a.source_title
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.source_title.to_lowercase())
|
||||||
|
};
|
||||||
|
expected_blocklist.sort_by(cmp_fn);
|
||||||
|
|
||||||
|
let blocklist_sort_option = SortOption {
|
||||||
|
name: "Source Title",
|
||||||
|
cmp_fn: Some(cmp_fn),
|
||||||
|
};
|
||||||
|
app_arc
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.sorting(vec![blocklist_sort_option]);
|
||||||
|
}
|
||||||
|
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||||
|
|
||||||
|
network.handle_radarr_event(RadarrEvent::GetBlocklist).await;
|
||||||
|
|
||||||
|
async_server.assert_async().await;
|
||||||
|
assert_eq!(
|
||||||
|
app_arc.lock().await.data.radarr_data.blocklist.items,
|
||||||
|
expected_blocklist
|
||||||
|
);
|
||||||
|
assert!(app_arc.lock().await.data.radarr_data.blocklist.sort_asc);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_blocklist_event_no_op_when_user_is_selecting_sort_options() {
|
||||||
|
let blocklist_json = json!({"records": [{
|
||||||
|
"id": 123,
|
||||||
|
"movieId": 1007,
|
||||||
|
"sourceTitle": "z movie",
|
||||||
|
"languages": [{"name": "English"}],
|
||||||
|
"quality": {"quality": {"name": "HD - 1080p"}},
|
||||||
|
"customFormats": [{"name": "English"}],
|
||||||
|
"date": "2024-02-10T07:28:45Z",
|
||||||
|
"protocol": "usenet",
|
||||||
|
"indexer": "DrunkenSlug (Prowlarr)",
|
||||||
|
"message": "test message",
|
||||||
|
"movie": {
|
||||||
|
"id": 1007,
|
||||||
|
"title": "z movie",
|
||||||
|
"tmdbId": 1234,
|
||||||
|
"originalLanguage": {"name": "English"},
|
||||||
|
"sizeOnDisk": 3543348019i64,
|
||||||
|
"status": "Downloaded",
|
||||||
|
"overview": "Blah blah blah",
|
||||||
|
"path": "/nfs/movies",
|
||||||
|
"studio": "21st Century Alex",
|
||||||
|
"genres": ["cool", "family", "fun"],
|
||||||
|
"year": 2023,
|
||||||
|
"monitored": true,
|
||||||
|
"hasFile": true,
|
||||||
|
"runtime": 120,
|
||||||
|
"qualityProfileId": 2222,
|
||||||
|
"minimumAvailability": "announced",
|
||||||
|
"certification": "R",
|
||||||
|
"tags": [1],
|
||||||
|
"ratings": {
|
||||||
|
"imdb": {"value": 9.9},
|
||||||
|
"tmdb": {"value": 9.9},
|
||||||
|
"rottenTomatoes": {"value": 9.9}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
"id": 456,
|
||||||
|
"movieId": 2001,
|
||||||
|
"sourceTitle": "A Movie",
|
||||||
|
"languages": [{"name": "English"}],
|
||||||
|
"quality": {"quality": {"name": "HD - 1080p"}},
|
||||||
|
"customFormats": [{"name": "English"}],
|
||||||
|
"date": "2024-02-10T07:28:45Z",
|
||||||
|
"protocol": "usenet",
|
||||||
|
"indexer": "DrunkenSlug (Prowlarr)",
|
||||||
|
"message": "test message",
|
||||||
|
"movie": {
|
||||||
|
"id": 2001,
|
||||||
|
"title": "A Movie",
|
||||||
|
"tmdbId": 1234,
|
||||||
|
"originalLanguage": {"name": "English"},
|
||||||
|
"sizeOnDisk": 3543348019i64,
|
||||||
|
"status": "Downloaded",
|
||||||
|
"overview": "Blah blah blah",
|
||||||
|
"path": "/nfs/movies",
|
||||||
|
"studio": "21st Century Alex",
|
||||||
|
"genres": ["cool", "family", "fun"],
|
||||||
|
"year": 2023,
|
||||||
|
"monitored": true,
|
||||||
|
"hasFile": true,
|
||||||
|
"runtime": 120,
|
||||||
|
"qualityProfileId": 2222,
|
||||||
|
"minimumAvailability": "announced",
|
||||||
|
"certification": "R",
|
||||||
|
"tags": [1],
|
||||||
|
"ratings": {
|
||||||
|
"imdb": {"value": 9.9},
|
||||||
|
"tmdb": {"value": 9.9},
|
||||||
|
"rottenTomatoes": {"value": 9.9}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}]});
|
||||||
|
let (async_server, app_arc, _server) = mock_radarr_api(
|
||||||
|
RequestMethod::Get,
|
||||||
|
None,
|
||||||
|
Some(blocklist_json),
|
||||||
|
None,
|
||||||
|
RadarrEvent::GetBlocklist.resource(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
app_arc.lock().await.data.radarr_data.blocklist.sort_asc = true;
|
||||||
|
app_arc
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.push_navigation_stack(ActiveRadarrBlock::BlocklistSortPrompt.into());
|
||||||
|
let cmp_fn = |a: &BlocklistItem, b: &BlocklistItem| {
|
||||||
|
a.source_title
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.source_title.to_lowercase())
|
||||||
|
};
|
||||||
|
let blocklist_sort_option = SortOption {
|
||||||
|
name: "Source Title",
|
||||||
|
cmp_fn: Some(cmp_fn),
|
||||||
|
};
|
||||||
|
app_arc
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.sorting(vec![blocklist_sort_option]);
|
||||||
|
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||||
|
|
||||||
|
network.handle_radarr_event(RadarrEvent::GetBlocklist).await;
|
||||||
|
|
||||||
|
async_server.assert_async().await;
|
||||||
|
assert!(app_arc
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.items
|
||||||
|
.is_empty());
|
||||||
|
assert!(app_arc.lock().await.data.radarr_data.blocklist.sort_asc);
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_get_collections_event(#[values(true, false)] use_custom_sorting: bool) {
|
async fn test_handle_get_collections_event(#[values(true, false)] use_custom_sorting: bool) {
|
||||||
@@ -2154,6 +2422,68 @@ mod test {
|
|||||||
assert!(!app_arc.lock().await.data.radarr_data.add_list_exclusion);
|
assert!(!app_arc.lock().await.data.radarr_data.add_list_exclusion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_clear_blocklist_event() {
|
||||||
|
let blocklist_items = vec![
|
||||||
|
BlocklistItem {
|
||||||
|
id: 1,
|
||||||
|
..blocklist_item()
|
||||||
|
},
|
||||||
|
BlocklistItem {
|
||||||
|
id: 2,
|
||||||
|
..blocklist_item()
|
||||||
|
},
|
||||||
|
BlocklistItem {
|
||||||
|
id: 3,
|
||||||
|
..blocklist_item()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let expected_request_json = json!({ "ids": [1, 2, 3]});
|
||||||
|
let (async_server, app_arc, _server) = mock_radarr_api(
|
||||||
|
RequestMethod::Delete,
|
||||||
|
Some(expected_request_json),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
RadarrEvent::ClearBlocklist.resource(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
app_arc
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.set_items(blocklist_items);
|
||||||
|
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||||
|
|
||||||
|
network
|
||||||
|
.handle_radarr_event(RadarrEvent::ClearBlocklist)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
async_server.assert_async().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_delete_blocklist_item_event() {
|
||||||
|
let resource = format!("{}/1", RadarrEvent::DeleteBlocklistItem.resource());
|
||||||
|
let (async_server, app_arc, _server) =
|
||||||
|
mock_radarr_api(RequestMethod::Delete, None, None, None, &resource).await;
|
||||||
|
app_arc
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.set_items(vec![blocklist_item()]);
|
||||||
|
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||||
|
|
||||||
|
network
|
||||||
|
.handle_radarr_event(RadarrEvent::DeleteBlocklistItem)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
async_server.assert_async().await;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_delete_download_event() {
|
async fn test_handle_delete_download_event() {
|
||||||
let resource = format!("{}/1", RadarrEvent::DeleteDownload.resource());
|
let resource = format!("{}/1", RadarrEvent::DeleteDownload.resource());
|
||||||
@@ -3311,6 +3641,22 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn blocklist_item() -> BlocklistItem {
|
||||||
|
BlocklistItem {
|
||||||
|
id: 1,
|
||||||
|
movie_id: 1,
|
||||||
|
source_title: "z movie".to_owned(),
|
||||||
|
languages: vec![language()],
|
||||||
|
quality: quality_wrapper(),
|
||||||
|
custom_formats: Some(vec![language()]),
|
||||||
|
date: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()),
|
||||||
|
protocol: "usenet".to_owned(),
|
||||||
|
indexer: "DrunkenSlug (Prowlarr)".to_owned(),
|
||||||
|
message: "test message".to_owned(),
|
||||||
|
movie: movie(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn collection() -> Collection {
|
fn collection() -> Collection {
|
||||||
Collection {
|
Collection {
|
||||||
id: 123,
|
id: 123,
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS};
|
||||||
|
use crate::ui::radarr_ui::blocklist::BlocklistUi;
|
||||||
|
use crate::ui::DrawUi;
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_ui_accepts() {
|
||||||
|
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
|
||||||
|
if BLOCKLIST_BLOCKS.contains(&active_radarr_block) {
|
||||||
|
assert!(BlocklistUi::accepts(active_radarr_block.into()));
|
||||||
|
} else {
|
||||||
|
assert!(!BlocklistUi::accepts(active_radarr_block.into()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
use crate::app::App;
|
||||||
|
use crate::models::radarr_models::BlocklistItem;
|
||||||
|
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS};
|
||||||
|
use crate::models::Route;
|
||||||
|
use crate::ui::styles::ManagarrStyle;
|
||||||
|
use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
|
||||||
|
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
||||||
|
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||||
|
use crate::ui::widgets::message::Message;
|
||||||
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
|
use crate::ui::DrawUi;
|
||||||
|
use ratatui::layout::{Alignment, Constraint, Rect};
|
||||||
|
use ratatui::style::{Style, Stylize};
|
||||||
|
use ratatui::text::{Line, Text};
|
||||||
|
use ratatui::widgets::{Cell, Row};
|
||||||
|
use ratatui::Frame;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "blocklist_ui_tests.rs"]
|
||||||
|
mod blocklist_ui_tests;
|
||||||
|
|
||||||
|
pub(super) struct BlocklistUi;
|
||||||
|
|
||||||
|
impl DrawUi for BlocklistUi {
|
||||||
|
fn accepts(route: Route) -> bool {
|
||||||
|
if let Route::Radarr(active_radarr_block, _) = route {
|
||||||
|
return BLOCKLIST_BLOCKS.contains(&active_radarr_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
|
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
|
||||||
|
match active_radarr_block {
|
||||||
|
ActiveRadarrBlock::Blocklist | ActiveRadarrBlock::BlocklistSortPrompt => {
|
||||||
|
draw_blocklist_table(f, app, area)
|
||||||
|
}
|
||||||
|
ActiveRadarrBlock::BlocklistItemDetails => {
|
||||||
|
draw_blocklist_table(f, app, area);
|
||||||
|
draw_blocklist_item_details_popup(f, app);
|
||||||
|
}
|
||||||
|
ActiveRadarrBlock::DeleteBlocklistItemPrompt => {
|
||||||
|
let prompt = format!(
|
||||||
|
"Do you want to remove this item from your blocklist: \n{}?",
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.blocklist
|
||||||
|
.current_selection()
|
||||||
|
.source_title
|
||||||
|
);
|
||||||
|
let confirmation_prompt = ConfirmationPrompt::new()
|
||||||
|
.title("Remove Item from Blocklist")
|
||||||
|
.prompt(&prompt)
|
||||||
|
.yes_no_value(app.data.radarr_data.prompt_confirm);
|
||||||
|
|
||||||
|
draw_blocklist_table(f, app, area);
|
||||||
|
f.render_widget(Popup::new(confirmation_prompt).size(Size::Prompt), f.size());
|
||||||
|
}
|
||||||
|
ActiveRadarrBlock::BlocklistClearAllItemsPrompt => {
|
||||||
|
let confirmation_prompt = ConfirmationPrompt::new()
|
||||||
|
.title("Clear Blocklist")
|
||||||
|
.prompt("Do you want to clear your blocklist?")
|
||||||
|
.yes_no_value(app.data.radarr_data.prompt_confirm);
|
||||||
|
|
||||||
|
draw_blocklist_table(f, app, area);
|
||||||
|
f.render_widget(
|
||||||
|
Popup::new(confirmation_prompt).size(Size::SmallPrompt),
|
||||||
|
f.size(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_blocklist_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
|
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
|
||||||
|
let current_selection = if app.data.radarr_data.blocklist.items.is_empty() {
|
||||||
|
BlocklistItem::default()
|
||||||
|
} else {
|
||||||
|
app.data.radarr_data.blocklist.current_selection().clone()
|
||||||
|
};
|
||||||
|
let blocklist_table_footer = app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.main_tabs
|
||||||
|
.get_active_tab_contextual_help();
|
||||||
|
|
||||||
|
let blocklist_row_mapping = |blocklist_item: &BlocklistItem| {
|
||||||
|
let BlocklistItem {
|
||||||
|
source_title,
|
||||||
|
languages,
|
||||||
|
quality,
|
||||||
|
custom_formats,
|
||||||
|
date,
|
||||||
|
movie,
|
||||||
|
..
|
||||||
|
} = blocklist_item;
|
||||||
|
|
||||||
|
movie.title.scroll_left_or_reset(
|
||||||
|
get_width_from_percentage(area, 20),
|
||||||
|
current_selection == *blocklist_item,
|
||||||
|
app.tick_count % app.ticks_until_scroll == 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
let languages_string = languages
|
||||||
|
.iter()
|
||||||
|
.map(|lang| lang.name.to_owned())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
let custom_formats_string = if let Some(formats) = custom_formats.as_ref() {
|
||||||
|
formats
|
||||||
|
.iter()
|
||||||
|
.map(|cf| cf.name.to_owned())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ")
|
||||||
|
} else {
|
||||||
|
"".to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
Row::new(vec![
|
||||||
|
Cell::from(movie.title.to_string()),
|
||||||
|
Cell::from(source_title.to_owned()),
|
||||||
|
Cell::from(languages_string),
|
||||||
|
Cell::from(quality.quality.name.to_owned()),
|
||||||
|
Cell::from(custom_formats_string),
|
||||||
|
Cell::from(date.to_string()),
|
||||||
|
])
|
||||||
|
.primary()
|
||||||
|
};
|
||||||
|
let blocklist_table = ManagarrTable::new(
|
||||||
|
Some(&mut app.data.radarr_data.blocklist),
|
||||||
|
blocklist_row_mapping,
|
||||||
|
)
|
||||||
|
.block(layout_block_top_border())
|
||||||
|
.loading(app.is_loading)
|
||||||
|
.footer(blocklist_table_footer)
|
||||||
|
.sorting(active_radarr_block == ActiveRadarrBlock::BlocklistSortPrompt)
|
||||||
|
.headers([
|
||||||
|
"Movie Title",
|
||||||
|
"Source Title",
|
||||||
|
"Languages",
|
||||||
|
"Quality",
|
||||||
|
"Formats",
|
||||||
|
"Date",
|
||||||
|
])
|
||||||
|
.constraints([
|
||||||
|
Constraint::Percentage(20),
|
||||||
|
Constraint::Percentage(35),
|
||||||
|
Constraint::Percentage(10),
|
||||||
|
Constraint::Percentage(10),
|
||||||
|
Constraint::Percentage(10),
|
||||||
|
Constraint::Percentage(15),
|
||||||
|
]);
|
||||||
|
|
||||||
|
f.render_widget(blocklist_table, area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_blocklist_item_details_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||||
|
let current_selection = if app.data.radarr_data.blocklist.items.is_empty() {
|
||||||
|
BlocklistItem::default()
|
||||||
|
} else {
|
||||||
|
app.data.radarr_data.blocklist.current_selection().clone()
|
||||||
|
};
|
||||||
|
let BlocklistItem {
|
||||||
|
source_title,
|
||||||
|
protocol,
|
||||||
|
indexer,
|
||||||
|
message,
|
||||||
|
..
|
||||||
|
} = current_selection;
|
||||||
|
let text = Text::from(vec![
|
||||||
|
Line::from(vec![
|
||||||
|
"Name: ".bold().secondary(),
|
||||||
|
source_title.to_owned().secondary(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Protocol: ".bold().secondary(),
|
||||||
|
protocol.to_owned().secondary(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Indexer: ".bold().secondary(),
|
||||||
|
indexer.to_owned().secondary(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Message: ".bold().secondary(),
|
||||||
|
message.to_owned().secondary(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let message = Message::new(text)
|
||||||
|
.title("Details")
|
||||||
|
.style(Style::new().secondary())
|
||||||
|
.alignment(Alignment::Left);
|
||||||
|
|
||||||
|
f.render_widget(Popup::new(message).size(Size::NarrowMessage), f.size());
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ use crate::models::radarr_models::{DiskSpace, DownloadRecord, Movie, RootFolder}
|
|||||||
use crate::models::servarr_data::radarr::radarr_data::RadarrData;
|
use crate::models::servarr_data::radarr::radarr_data::RadarrData;
|
||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
use crate::ui::draw_tabs;
|
use crate::ui::draw_tabs;
|
||||||
|
use crate::ui::radarr_ui::blocklist::BlocklistUi;
|
||||||
use crate::ui::radarr_ui::collections::CollectionsUi;
|
use crate::ui::radarr_ui::collections::CollectionsUi;
|
||||||
use crate::ui::radarr_ui::downloads::DownloadsUi;
|
use crate::ui::radarr_ui::downloads::DownloadsUi;
|
||||||
use crate::ui::radarr_ui::indexers::IndexersUi;
|
use crate::ui::radarr_ui::indexers::IndexersUi;
|
||||||
@@ -27,6 +28,7 @@ use crate::ui::widgets::loading_block::LoadingBlock;
|
|||||||
use crate::ui::DrawUi;
|
use crate::ui::DrawUi;
|
||||||
use crate::utils::convert_to_gb;
|
use crate::utils::convert_to_gb;
|
||||||
|
|
||||||
|
mod blocklist;
|
||||||
mod collections;
|
mod collections;
|
||||||
mod downloads;
|
mod downloads;
|
||||||
mod indexers;
|
mod indexers;
|
||||||
@@ -57,6 +59,7 @@ impl DrawUi for RadarrUi {
|
|||||||
_ if IndexersUi::accepts(route) => IndexersUi::draw(f, app, content_area),
|
_ if IndexersUi::accepts(route) => IndexersUi::draw(f, app, content_area),
|
||||||
_ if RootFoldersUi::accepts(route) => RootFoldersUi::draw(f, app, content_area),
|
_ if RootFoldersUi::accepts(route) => RootFoldersUi::draw(f, app, content_area),
|
||||||
_ if SystemUi::accepts(route) => SystemUi::draw(f, app, content_area),
|
_ if SystemUi::accepts(route) => SystemUi::draw(f, app, content_area),
|
||||||
|
_ if BlocklistUi::accepts(route) => BlocklistUi::draw(f, app, content_area),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ pub struct Message<'a> {
|
|||||||
text: Text<'a>,
|
text: Text<'a>,
|
||||||
title: &'a str,
|
title: &'a str,
|
||||||
style: Style,
|
style: Style,
|
||||||
|
alignment: Alignment,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Message<'a> {
|
impl<'a> Message<'a> {
|
||||||
@@ -25,6 +26,7 @@ impl<'a> Message<'a> {
|
|||||||
text: message.into(),
|
text: message.into(),
|
||||||
title: "Error",
|
title: "Error",
|
||||||
style: Style::new().failure().bold(),
|
style: Style::new().failure().bold(),
|
||||||
|
alignment: Alignment::Center,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,10 +40,15 @@ impl<'a> Message<'a> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn alignment(mut self, alignment: Alignment) -> Self {
|
||||||
|
self.alignment = alignment;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn render_message(self, area: Rect, buf: &mut Buffer) {
|
fn render_message(self, area: Rect, buf: &mut Buffer) {
|
||||||
Paragraph::new(self.text)
|
Paragraph::new(self.text)
|
||||||
.style(self.style)
|
.style(self.style)
|
||||||
.alignment(Alignment::Center)
|
.alignment(self.alignment)
|
||||||
.block(title_block_centered(self.title).style(self.style))
|
.block(title_block_centered(self.title).style(self.style))
|
||||||
.wrap(Wrap { trim: true })
|
.wrap(Wrap { trim: true })
|
||||||
.render(area, buf);
|
.render(area, buf);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ mod tests {
|
|||||||
use crate::ui::styles::ManagarrStyle;
|
use crate::ui::styles::ManagarrStyle;
|
||||||
use crate::ui::widgets::message::Message;
|
use crate::ui::widgets::message::Message;
|
||||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||||
|
use ratatui::layout::Alignment;
|
||||||
use ratatui::style::{Style, Stylize};
|
use ratatui::style::{Style, Stylize};
|
||||||
use ratatui::text::Text;
|
use ratatui::text::Text;
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ mod tests {
|
|||||||
assert_eq!(message.text, Text::from(test_message));
|
assert_eq!(message.text, Text::from(test_message));
|
||||||
assert_str_eq!(message.title, "Error");
|
assert_str_eq!(message.title, "Error");
|
||||||
assert_eq!(message.style, Style::new().failure().bold());
|
assert_eq!(message.style, Style::new().failure().bold());
|
||||||
|
assert_eq!(message.alignment, Alignment::Center);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -27,6 +29,7 @@ mod tests {
|
|||||||
assert_str_eq!(message.title, title);
|
assert_str_eq!(message.title, title);
|
||||||
assert_eq!(message.text, Text::from(test_message));
|
assert_eq!(message.text, Text::from(test_message));
|
||||||
assert_eq!(message.style, Style::new().failure().bold());
|
assert_eq!(message.style, Style::new().failure().bold());
|
||||||
|
assert_eq!(message.alignment, Alignment::Center);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -39,5 +42,18 @@ mod tests {
|
|||||||
assert_eq!(message.style, style);
|
assert_eq!(message.style, style);
|
||||||
assert_eq!(message.text, Text::from(test_message));
|
assert_eq!(message.text, Text::from(test_message));
|
||||||
assert_str_eq!(message.title, "Error");
|
assert_str_eq!(message.title, "Error");
|
||||||
|
assert_eq!(message.alignment, Alignment::Center);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_message_alignment() {
|
||||||
|
let test_message = "This is a message";
|
||||||
|
|
||||||
|
let message = Message::new(test_message).alignment(Alignment::Left);
|
||||||
|
|
||||||
|
assert_eq!(message.alignment, Alignment::Left);
|
||||||
|
assert_eq!(message.text, Text::from(test_message));
|
||||||
|
assert_str_eq!(message.title, "Error");
|
||||||
|
assert_eq!(message.style, Style::new().failure().bold());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ use ratatui::widgets::{Block, Clear, Paragraph, Widget};
|
|||||||
mod popup_tests;
|
mod popup_tests;
|
||||||
|
|
||||||
pub enum Size {
|
pub enum Size {
|
||||||
|
SmallPrompt,
|
||||||
Prompt,
|
Prompt,
|
||||||
LargePrompt,
|
LargePrompt,
|
||||||
Message,
|
Message,
|
||||||
|
NarrowMessage,
|
||||||
LargeMessage,
|
LargeMessage,
|
||||||
InputBox,
|
InputBox,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
@@ -24,9 +26,11 @@ pub enum Size {
|
|||||||
impl Size {
|
impl Size {
|
||||||
pub fn to_percent(&self) -> (u16, u16) {
|
pub fn to_percent(&self) -> (u16, u16) {
|
||||||
match self {
|
match self {
|
||||||
|
Size::SmallPrompt => (20, 20),
|
||||||
Size::Prompt => (35, 35),
|
Size::Prompt => (35, 35),
|
||||||
Size::LargePrompt => (70, 45),
|
Size::LargePrompt => (70, 45),
|
||||||
Size::Message => (25, 8),
|
Size::Message => (25, 8),
|
||||||
|
Size::NarrowMessage => (50, 20),
|
||||||
Size::LargeMessage => (25, 25),
|
Size::LargeMessage => (25, 25),
|
||||||
Size::InputBox => (30, 13),
|
Size::InputBox => (30, 13),
|
||||||
Size::Dropdown => (20, 30),
|
Size::Dropdown => (20, 30),
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dimensions_to_percent() {
|
fn test_dimensions_to_percent() {
|
||||||
|
assert_eq!(Size::SmallPrompt.to_percent(), (20, 20));
|
||||||
assert_eq!(Size::Prompt.to_percent(), (35, 35));
|
assert_eq!(Size::Prompt.to_percent(), (35, 35));
|
||||||
assert_eq!(Size::LargePrompt.to_percent(), (70, 45));
|
assert_eq!(Size::LargePrompt.to_percent(), (70, 45));
|
||||||
assert_eq!(Size::Message.to_percent(), (25, 8));
|
assert_eq!(Size::Message.to_percent(), (25, 8));
|
||||||
|
assert_eq!(Size::NarrowMessage.to_percent(), (50, 20));
|
||||||
assert_eq!(Size::LargeMessage.to_percent(), (25, 25));
|
assert_eq!(Size::LargeMessage.to_percent(), (25, 25));
|
||||||
assert_eq!(Size::InputBox.to_percent(), (30, 13));
|
assert_eq!(Size::InputBox.to_percent(), (30, 13));
|
||||||
assert_eq!(Size::Dropdown.to_percent(), (20, 30));
|
assert_eq!(Size::Dropdown.to_percent(), (20, 30));
|
||||||
|
|||||||