diff --git a/Cargo.toml b/Cargo.toml index 8bb63a7..90717ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "managarr" -version = "0.0.18" +version = "0.0.19" authors = ["Alex Clarke "] description = "A TUI for managing *arr servers" keywords = ["managarr", "tui-rs", "dashboard", "servarr"] diff --git a/README.md b/README.md index 5251c8b..b77b170 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ tautulli: - [ ] Manage your quality profiles - [ ] Manage your quality definitions - [ ] Manage your indexers -- [x] View and browse logs, tasks, and events queues +- [x] View and browse logs, tasks, events queues, and updates - [x] Manually trigger scheduled tasks ### Sonarr diff --git a/src/app/key_binding.rs b/src/app/key_binding.rs index 5027fbf..68866a3 100644 --- a/src/app/key_binding.rs +++ b/src/app/key_binding.rs @@ -23,6 +23,7 @@ generate_keybindings! { tasks, refresh, update, + queue, home, end, delete, @@ -91,7 +92,11 @@ pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings { }, update: KeyBinding { key: Key::Char('u'), - desc: "Update All", + desc: "Update", + }, + queue: KeyBinding { + key: Key::Char('z'), + desc: "Queue", }, home: KeyBinding { key: Key::Home, diff --git a/src/app/radarr.rs b/src/app/radarr.rs index 54a0304..92a0aec 100644 --- a/src/app/radarr.rs +++ b/src/app/radarr.rs @@ -54,6 +54,7 @@ pub struct RadarrData<'a> { pub log_details: StatefulList, pub tasks: StatefulTable, pub queued_events: StatefulTable, + pub updates: ScrollableText, pub prompt_confirm_action: Option, pub main_tabs: TabState, pub movie_info_tabs: TabState, @@ -278,6 +279,7 @@ impl<'a> Default for RadarrData<'a> { log_details: StatefulList::default(), tasks: StatefulTable::default(), queued_events: StatefulTable::default(), + updates: ScrollableText::default(), prompt_confirm_action: None, search: HorizontallyScrollableText::default(), filter: HorizontallyScrollableText::default(), @@ -320,7 +322,7 @@ impl<'a> Default for RadarrData<'a> { title: "System", route: ActiveRadarrBlock::System.into(), help: "", - contextual_help: Some(" open tasks | open queue | open logs | refresh") + contextual_help: Some(" open tasks | open queue | open logs | open updates | refresh") } ]), movie_info_tabs: TabState::new(vec![ @@ -418,9 +420,10 @@ pub enum ActiveRadarrBlock { RootFolders, System, SystemLogs, + SystemQueue, SystemTasks, SystemTaskStartConfirmPrompt, - SystemQueue, + SystemUpdates, UpdateAndScanPrompt, UpdateAllCollectionsPrompt, UpdateAllMoviesPrompt, @@ -519,11 +522,12 @@ pub static DELETE_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 3] = [ ActiveRadarrBlock::DeleteMovieToggleAddListExclusion, ActiveRadarrBlock::DeleteMovieConfirmPrompt, ]; -pub static SYSTEM_DETAILS_BLOCKS: [ActiveRadarrBlock; 4] = [ +pub static SYSTEM_DETAILS_BLOCKS: [ActiveRadarrBlock; 5] = [ ActiveRadarrBlock::SystemLogs, - ActiveRadarrBlock::SystemTasks, ActiveRadarrBlock::SystemQueue, + ActiveRadarrBlock::SystemTasks, ActiveRadarrBlock::SystemTaskStartConfirmPrompt, + ActiveRadarrBlock::SystemUpdates, ]; impl From for Route { @@ -580,6 +584,11 @@ impl<'a> App<'a> { .dispatch_network_event(RadarrEvent::GetLogs.into()) .await; } + ActiveRadarrBlock::SystemUpdates => { + self + .dispatch_network_event(RadarrEvent::GetUpdates.into()) + .await; + } ActiveRadarrBlock::AddMovieSearchResults => { self .dispatch_network_event(RadarrEvent::SearchNewMovie.into()) diff --git a/src/app/radarr_tests.rs b/src/app/radarr_tests.rs index 087fadf..83b659d 100644 --- a/src/app/radarr_tests.rs +++ b/src/app/radarr_tests.rs @@ -291,6 +291,7 @@ mod tests { assert!(radarr_data.log_details.items.is_empty()); assert!(radarr_data.tasks.items.is_empty()); assert!(radarr_data.queued_events.items.is_empty()); + assert!(radarr_data.updates.get_text().is_empty()); assert!(radarr_data.prompt_confirm_action.is_none()); assert!(radarr_data.search.text.is_empty()); assert!(radarr_data.filter.text.is_empty()); @@ -355,7 +356,7 @@ mod tests { assert!(radarr_data.main_tabs.tabs[4].help.is_empty()); assert_eq!( radarr_data.main_tabs.tabs[4].contextual_help, - Some(" open tasks | open queue | open logs | refresh") + Some(" open tasks | open queue | open logs | open updates | refresh") ); assert_eq!(radarr_data.movie_info_tabs.tabs.len(), 6); @@ -708,6 +709,23 @@ mod tests { assert_eq!(app.tick_count, 0); } + #[tokio::test] + async fn test_dispatch_by_system_updates_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::SystemUpdates) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetUpdates.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + #[tokio::test] async fn test_dispatch_by_add_movie_search_results_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); diff --git a/src/handlers/handler_test_utils.rs b/src/handlers/handler_test_utils.rs index 31abf61..440ece2 100644 --- a/src/handlers/handler_test_utils.rs +++ b/src/handlers/handler_test_utils.rs @@ -238,6 +238,25 @@ mod test_utils { }; } + #[macro_export] + macro_rules! test_scrollable_text_scroll { + ($func:ident, $handler:ident, $data_ref:ident, $block:expr) => { + #[test] + fn $func() { + let mut app = App::default(); + app.data.radarr_data.$data_ref = ScrollableText::with_string("Test 1\nTest 2".to_owned()); + + $handler::with(&DEFAULT_KEYBINDINGS.up.key, &mut app, &$block, &None).handle(); + + assert_eq!(app.data.radarr_data.$data_ref.offset, 0); + + $handler::with(&DEFAULT_KEYBINDINGS.down.key, &mut app, &$block, &None).handle(); + + assert_eq!(app.data.radarr_data.$data_ref.offset, 1); + } + }; + } + #[macro_export] macro_rules! test_iterable_home_and_end { ($func:ident, $handler:ident, $data_ref:ident, $block:expr, $context:expr) => { @@ -373,6 +392,25 @@ mod test_utils { }; } + #[macro_export] + macro_rules! test_scrollable_text_home_and_end { + ($func:ident, $handler:ident, $data_ref:ident, $block:expr) => { + #[test] + fn $func() { + let mut app = App::default(); + app.data.radarr_data.$data_ref = ScrollableText::with_string("Test 1\nTest 2".to_owned()); + + $handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &None).handle(); + + assert_eq!(app.data.radarr_data.$data_ref.offset, 1); + + $handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &None).handle(); + + assert_eq!(app.data.radarr_data.$data_ref.offset, 0); + } + }; + } + #[macro_export] macro_rules! test_text_box_home_end_keys { ($handler:ident, $block:expr, $field:ident) => { diff --git a/src/handlers/radarr_handlers/mod.rs b/src/handlers/radarr_handlers/mod.rs index 59f09af..f9e148f 100644 --- a/src/handlers/radarr_handlers/mod.rs +++ b/src/handlers/radarr_handlers/mod.rs @@ -574,7 +574,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b _ if *key == DEFAULT_KEYBINDINGS.refresh.key => { self.app.should_refresh = true; } - _ if *key == DEFAULT_KEYBINDINGS.update.key => { + _ if *key == DEFAULT_KEYBINDINGS.queue.key => { self .app .push_navigation_stack(ActiveRadarrBlock::SystemQueue.into()); @@ -596,6 +596,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b .app .push_navigation_stack(ActiveRadarrBlock::SystemTasks.into()); } + _ if *key == DEFAULT_KEYBINDINGS.update.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::SystemUpdates.into()); + } _ => (), }, ActiveRadarrBlock::AddRootFolderPrompt => { diff --git a/src/handlers/radarr_handlers/movie_details_handler_tests.rs b/src/handlers/radarr_handlers/movie_details_handler_tests.rs index b5bce19..4743d9f 100644 --- a/src/handlers/radarr_handlers/movie_details_handler_tests.rs +++ b/src/handlers/radarr_handlers/movie_details_handler_tests.rs @@ -23,35 +23,19 @@ mod tests { use strum::IntoEnumIterator; use crate::models::radarr_models::ReleaseField; - use crate::{simple_stateful_iterable_vec, test_enum_scroll, test_iterable_scroll}; + use crate::{ + simple_stateful_iterable_vec, test_enum_scroll, test_iterable_scroll, + test_scrollable_text_scroll, + }; use super::*; - #[test] - fn test_movie_details_scroll() { - let mut app = App::default(); - app.data.radarr_data.movie_details = ScrollableText::with_string("Test 1\nTest 2".to_owned()); - - MovieDetailsHandler::with( - &DEFAULT_KEYBINDINGS.up.key, - &mut app, - &ActiveRadarrBlock::MovieDetails, - &None, - ) - .handle(); - - assert_eq!(app.data.radarr_data.movie_details.offset, 0); - - MovieDetailsHandler::with( - &DEFAULT_KEYBINDINGS.down.key, - &mut app, - &ActiveRadarrBlock::MovieDetails, - &None, - ) - .handle(); - - assert_eq!(app.data.radarr_data.movie_details.offset, 1); - } + test_scrollable_text_scroll!( + test_movie_details_scroll, + MovieDetailsHandler, + movie_details, + ActiveRadarrBlock::MovieDetails + ); test_iterable_scroll!( test_movie_history_scroll, @@ -113,35 +97,17 @@ mod tests { use crate::models::radarr_models::ReleaseField; use crate::{ extended_stateful_iterable_vec, test_enum_home_and_end, test_iterable_home_and_end, + test_scrollable_text_home_and_end, }; use super::*; - #[test] - fn test_movie_details_home_end() { - let mut app = App::default(); - app.data.radarr_data.movie_details = ScrollableText::with_string("Test 1\nTest 2".to_owned()); - - MovieDetailsHandler::with( - &DEFAULT_KEYBINDINGS.end.key, - &mut app, - &ActiveRadarrBlock::MovieDetails, - &None, - ) - .handle(); - - assert_eq!(app.data.radarr_data.movie_details.offset, 1); - - MovieDetailsHandler::with( - &DEFAULT_KEYBINDINGS.home.key, - &mut app, - &ActiveRadarrBlock::MovieDetails, - &None, - ) - .handle(); - - assert_eq!(app.data.radarr_data.movie_details.offset, 0); - } + test_scrollable_text_home_and_end!( + test_movie_details_home_end, + MovieDetailsHandler, + movie_details, + ActiveRadarrBlock::MovieDetails + ); test_iterable_home_and_end!( test_movie_history_home_end, diff --git a/src/handlers/radarr_handlers/radarr_handler_tests.rs b/src/handlers/radarr_handlers/radarr_handler_tests.rs index cf43f18..56a5706 100644 --- a/src/handlers/radarr_handlers/radarr_handler_tests.rs +++ b/src/handlers/radarr_handlers/radarr_handler_tests.rs @@ -921,7 +921,7 @@ mod tests { ActiveRadarrBlock::Collections, ActiveRadarrBlock::UpdateAllCollectionsPrompt )] - #[case(ActiveRadarrBlock::System, ActiveRadarrBlock::SystemQueue)] + #[case(ActiveRadarrBlock::System, ActiveRadarrBlock::SystemUpdates)] fn test_update_key( #[case] active_radarr_block: ActiveRadarrBlock, #[case] expected_radarr_block: ActiveRadarrBlock, @@ -939,6 +939,24 @@ mod tests { assert_eq!(app.get_current_route(), &expected_radarr_block.into()); } + #[test] + fn test_queue_key() { + let mut app = App::default(); + + RadarrHandler::with( + &DEFAULT_KEYBINDINGS.queue.key, + &mut app, + &ActiveRadarrBlock::System, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::SystemQueue.into() + ); + } + #[rstest] fn test_refresh_key( #[values( @@ -1252,7 +1270,8 @@ mod tests { ActiveRadarrBlock::System, ActiveRadarrBlock::SystemLogs, ActiveRadarrBlock::SystemTasks, - ActiveRadarrBlock::SystemQueue + ActiveRadarrBlock::SystemQueue, + ActiveRadarrBlock::SystemUpdates )] active_radarr_block: ActiveRadarrBlock, ) { diff --git a/src/handlers/radarr_handlers/system_details_handler.rs b/src/handlers/radarr_handlers/system_details_handler.rs index 0a26f2d..165f841 100644 --- a/src/handlers/radarr_handlers/system_details_handler.rs +++ b/src/handlers/radarr_handlers/system_details_handler.rs @@ -40,6 +40,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler match self.active_radarr_block { ActiveRadarrBlock::SystemLogs => self.app.data.radarr_data.log_details.scroll_up(), ActiveRadarrBlock::SystemTasks => self.app.data.radarr_data.tasks.scroll_up(), + ActiveRadarrBlock::SystemUpdates => self.app.data.radarr_data.updates.scroll_up(), ActiveRadarrBlock::SystemQueue => self.app.data.radarr_data.queued_events.scroll_up(), _ => (), } @@ -49,6 +50,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler match self.active_radarr_block { ActiveRadarrBlock::SystemLogs => self.app.data.radarr_data.log_details.scroll_down(), ActiveRadarrBlock::SystemTasks => self.app.data.radarr_data.tasks.scroll_down(), + ActiveRadarrBlock::SystemUpdates => self.app.data.radarr_data.updates.scroll_down(), ActiveRadarrBlock::SystemQueue => self.app.data.radarr_data.queued_events.scroll_down(), _ => (), } @@ -58,6 +60,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler match self.active_radarr_block { ActiveRadarrBlock::SystemLogs => self.app.data.radarr_data.log_details.scroll_to_top(), ActiveRadarrBlock::SystemTasks => self.app.data.radarr_data.tasks.scroll_to_top(), + ActiveRadarrBlock::SystemUpdates => self.app.data.radarr_data.updates.scroll_to_top(), ActiveRadarrBlock::SystemQueue => self.app.data.radarr_data.queued_events.scroll_to_top(), _ => (), } @@ -67,6 +70,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler match self.active_radarr_block { ActiveRadarrBlock::SystemLogs => self.app.data.radarr_data.log_details.scroll_to_bottom(), ActiveRadarrBlock::SystemTasks => self.app.data.radarr_data.tasks.scroll_to_bottom(), + ActiveRadarrBlock::SystemUpdates => self.app.data.radarr_data.updates.scroll_to_bottom(), ActiveRadarrBlock::SystemQueue => self.app.data.radarr_data.queued_events.scroll_to_bottom(), _ => (), } @@ -126,12 +130,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler fn handle_esc(&mut self) { match self.active_radarr_block { - ActiveRadarrBlock::SystemLogs - | ActiveRadarrBlock::SystemTasks - | ActiveRadarrBlock::SystemQueue => { + ActiveRadarrBlock::SystemLogs => { self.app.data.radarr_data.reset_log_details_list(); self.app.pop_navigation_stack() } + ActiveRadarrBlock::SystemQueue + | ActiveRadarrBlock::SystemTasks + | ActiveRadarrBlock::SystemUpdates => self.app.pop_navigation_stack(), ActiveRadarrBlock::SystemTaskStartConfirmPrompt => { self.app.pop_navigation_stack(); self.app.data.radarr_data.prompt_confirm = false; diff --git a/src/handlers/radarr_handlers/system_details_handler_tests.rs b/src/handlers/radarr_handlers/system_details_handler_tests.rs index 1e764ea..6b89dd7 100644 --- a/src/handlers/radarr_handlers/system_details_handler_tests.rs +++ b/src/handlers/radarr_handlers/system_details_handler_tests.rs @@ -13,8 +13,8 @@ mod tests { mod test_handle_scroll_up_and_down { use rstest::rstest; - use crate::models::HorizontallyScrollableText; - use crate::{simple_stateful_iterable_vec, test_iterable_scroll}; + use crate::models::{HorizontallyScrollableText, ScrollableText}; + use crate::{simple_stateful_iterable_vec, test_iterable_scroll, test_scrollable_text_scroll}; use super::*; @@ -47,11 +47,20 @@ mod tests { None, name ); + + test_scrollable_text_scroll!( + test_system_updates_scroll, + SystemDetailsHandler, + updates, + ActiveRadarrBlock::SystemUpdates + ); } mod test_handle_home_end { - use crate::models::HorizontallyScrollableText; - use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; + use crate::models::{HorizontallyScrollableText, ScrollableText}; + use crate::{ + extended_stateful_iterable_vec, test_iterable_home_and_end, test_scrollable_text_home_and_end, + }; use super::*; @@ -84,13 +93,21 @@ mod tests { None, name ); + + test_scrollable_text_home_and_end!( + test_system_updates_home_end, + SystemDetailsHandler, + updates, + ActiveRadarrBlock::SystemUpdates + ); } mod test_handle_left_right_action { - use super::*; use pretty_assertions::assert_eq; use rstest::rstest; + use super::*; + #[test] fn test_handle_log_details_left_right() { let active_radarr_block = ActiveRadarrBlock::SystemLogs; @@ -219,9 +236,10 @@ mod tests { } mod test_handle_submit { - use crate::network::radarr_network::RadarrEvent; use pretty_assertions::assert_eq; + use crate::network::radarr_network::RadarrEvent; + use super::*; const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; @@ -294,9 +312,10 @@ mod tests { } mod test_handle_esc { - use crate::models::HorizontallyScrollableText; use pretty_assertions::assert_eq; + use crate::models::HorizontallyScrollableText; + use super::*; const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; @@ -354,6 +373,23 @@ mod tests { assert_eq!(app.get_current_route(), &ActiveRadarrBlock::System.into()); } + #[test] + fn test_esc_system_updates() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::System.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) + .handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::System.into()); + } + #[test] fn test_system_tasks_start_task_prompt_esc() { let mut app = App::default(); @@ -387,7 +423,8 @@ mod tests { #[values( ActiveRadarrBlock::SystemLogs, ActiveRadarrBlock::SystemTasks, - ActiveRadarrBlock::SystemQueue + ActiveRadarrBlock::SystemQueue, + ActiveRadarrBlock::SystemUpdates )] active_radarr_block: ActiveRadarrBlock, ) { diff --git a/src/models/radarr_models.rs b/src/models/radarr_models.rs index 330ea4b..91009ad 100644 --- a/src/models/radarr_models.rs +++ b/src/models/radarr_models.rs @@ -450,3 +450,21 @@ pub struct QueueEvent { pub ended: Option>, pub duration: Option, } + +#[derive(Default, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Update { + pub version: String, + pub release_date: DateTime, + pub installed: bool, + pub latest: bool, + pub installed_on: Option>, + pub changes: UpdateChanges, +} + +#[derive(Default, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UpdateChanges { + pub new: Option>, + pub fixed: Option>, +} diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index 7c0c1bb..f61ae02 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -12,7 +12,7 @@ use crate::models::radarr_models::{ AddMovieBody, AddMovieSearchResult, AddOptions, AddRootFolderBody, Collection, CollectionMovie, CommandBody, Credit, CreditType, DiskSpace, DownloadRecord, DownloadsResponse, LogResponse, Movie, MovieCommandBody, MovieHistoryItem, QualityProfile, QueueEvent, Release, - ReleaseDownloadBody, RootFolder, SystemStatus, Tag, Task, + ReleaseDownloadBody, RootFolder, SystemStatus, Tag, Task, Update, }; use crate::models::{HorizontallyScrollableText, Route, Scrollable, ScrollableText}; use crate::network::{Network, NetworkEvent, RequestMethod, RequestProps}; @@ -47,6 +47,7 @@ pub enum RadarrEvent { GetStatus, GetTags, GetTasks, + GetUpdates, HealthCheck, SearchNewMovie, StartTask, @@ -80,6 +81,7 @@ impl RadarrEvent { RadarrEvent::GetStatus => "/system/status", RadarrEvent::GetTags => "/tag", RadarrEvent::GetTasks => "/system/task", + RadarrEvent::GetUpdates => "/update", RadarrEvent::StartTask | RadarrEvent::GetQueuedEvents | RadarrEvent::TriggerAutomaticSearch @@ -124,6 +126,7 @@ impl<'a, 'b> Network<'a, 'b> { RadarrEvent::GetStatus => self.get_status().await, RadarrEvent::GetTags => self.get_tags().await, RadarrEvent::GetTasks => self.get_tasks().await, + RadarrEvent::GetUpdates => self.get_updates().await, RadarrEvent::HealthCheck => self.get_healthcheck().await, RadarrEvent::SearchNewMovie => self.search_movie().await, RadarrEvent::StartTask => self.start_task().await, @@ -742,6 +745,97 @@ impl<'a, 'b> Network<'a, 'b> { .await; } + async fn get_updates(&self) { + info!("Fetching Radarr updates"); + + let request_props = self + .radarr_request_props_from( + RadarrEvent::GetUpdates.resource(), + RequestMethod::Get, + None::<()>, + ) + .await; + + self + .handle_request::<(), Vec>(request_props, |updates_vec, mut app| { + let latest_installed = if updates_vec + .iter() + .any(|update| update.latest && update.installed_on.is_some()) + { + "already".to_owned() + } else { + "not".to_owned() + }; + let updates = updates_vec + .into_iter() + .map(|update| { + let install_status = if update.installed_on.is_some() { + if update.installed { + "(Currently Installed)".to_owned() + } else { + "(Previously Installed)".to_owned() + } + } else { + String::new() + }; + let vec_to_bullet_points = |vec: Vec| { + vec + .iter() + .map(|change| format!(" * {}", change)) + .collect::>() + .join("\n") + }; + + let mut update_info = formatdoc!( + "{} - {} {} + {}", + update.version, + update.release_date, + install_status, + "-".repeat(200) + ); + + if let Some(new_changes) = update.changes.new { + let changes = vec_to_bullet_points(new_changes); + update_info = formatdoc!( + "{} + New: + {}", + update_info, + changes + ) + } + + if let Some(fixes) = update.changes.fixed { + let fixes = vec_to_bullet_points(fixes); + update_info = formatdoc!( + "{} + Fixed: + {}", + update_info, + fixes + ); + } + + update_info + }) + .reduce(|version_1, version_2| format!("{}\n\n\n{}", version_1, version_2)) + .unwrap(); + + app.data.radarr_data.updates = ScrollableText::with_string(formatdoc!( + "{} + + {}", + format!( + "The latest version of Radarr is {} installed", + latest_installed + ), + updates + )); + }) + .await; + } + async fn add_tag(&self, tag: String) { info!("Adding a new Radarr tag"); diff --git a/src/network/radarr_network_tests.rs b/src/network/radarr_network_tests.rs index ddf664a..dad5dea 100644 --- a/src/network/radarr_network_tests.rs +++ b/src/network/radarr_network_tests.rs @@ -1065,6 +1065,95 @@ mod test { ); } + #[tokio::test] + async fn test_handle_get_updates_event() { + let tasks_json = json!([{ + "version": "4.3.2.1", + "releaseDate": "2023-04-15T02:02:53Z", + "installed": true, + "installedOn": "2023-04-15T02:02:53Z", + "latest": true, + "changes": { + "new": [ + "Cool new thing" + ], + "fixed": [ + "Some bugs killed" + ] + }, + }, + { + "version": "3.2.1.0", + "releaseDate": "2023-04-15T02:02:53Z", + "installed": false, + "installedOn": "2023-04-15T02:02:53Z", + "latest": false, + "changes": { + "new": [ + "Cool new thing (old)", + "Other cool new thing (old)" + ], + }, + }, + { + "version": "2.1.0", + "releaseDate": "2023-04-15T02:02:53Z", + "installed": false, + "latest": false, + "changes": { + "fixed": [ + "Killed bug 1", + "Fixed bug 2" + ] + }, + }]); + let line_break = "-".repeat(200); + let expected_text = ScrollableText::with_string(formatdoc!( + " + The latest version of Radarr is already installed + + 4.3.2.1 - 2023-04-15 02:02:53 UTC (Currently Installed) + {} + New: + * Cool new thing + Fixed: + * Some bugs killed + + + 3.2.1.0 - 2023-04-15 02:02:53 UTC (Previously Installed) + {} + New: + * Cool new thing (old) + * Other cool new thing (old) + + + 2.1.0 - 2023-04-15 02:02:53 UTC + {} + Fixed: + * Killed bug 1 + * Fixed bug 2", + line_break.clone(), + line_break.clone(), + line_break + )); + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(tasks_json), + RadarrEvent::GetUpdates.resource(), + ) + .await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network.handle_radarr_event(RadarrEvent::GetUpdates).await; + + async_server.assert_async().await; + assert_str_eq!( + app_arc.lock().await.data.radarr_data.updates.get_text(), + expected_text.get_text() + ); + } + #[tokio::test] async fn test_add_tag() { let (async_server, app_arc, _server) = mock_radarr_api( diff --git a/src/ui/radarr_ui/system_details_ui.rs b/src/ui/radarr_ui/system_details_ui.rs index 2b219ab..de7dae1 100644 --- a/src/ui/radarr_ui/system_details_ui.rs +++ b/src/ui/radarr_ui/system_details_ui.rs @@ -1,3 +1,9 @@ +use tui::backend::Backend; +use tui::layout::Rect; +use tui::text::{Span, Text}; +use tui::widgets::{Cell, ListItem, Paragraph, Row}; +use tui::Frame; + use crate::app::radarr::ActiveRadarrBlock; use crate::app::App; use crate::models::Route; @@ -9,13 +15,8 @@ use crate::ui::radarr_ui::system_ui::{ use crate::ui::utils::{borderless_block, style_primary, title_block}; use crate::ui::{ draw_help, draw_large_popup_over, draw_list_box, draw_medium_popup_over, draw_prompt_box, - draw_prompt_popup_over, draw_table, DrawUi, ListProps, TableProps, + draw_prompt_popup_over, draw_table, loading, DrawUi, ListProps, TableProps, }; -use tui::backend::Backend; -use tui::layout::Rect; -use tui::text::{Span, Text}; -use tui::widgets::{Cell, ListItem, Row}; -use tui::Frame; pub(super) struct SystemDetailsUi {} @@ -42,6 +43,13 @@ impl DrawUi for SystemDetailsUi { draw_system_ui_layout, draw_queued_events, ), + ActiveRadarrBlock::SystemUpdates => draw_large_popup_over( + f, + app, + content_rect, + draw_system_ui_layout, + draw_updates_popup, + ), _ => (), } } @@ -125,3 +133,18 @@ fn draw_start_task_prompt(f: &mut Frame<'_, B>, app: &mut App<'_>, p app.data.radarr_data.prompt_confirm, ); } + +fn draw_updates_popup(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { + let updates = app.data.radarr_data.updates.get_text(); + let block = title_block("Updates"); + + if !updates.is_empty() { + let updates_paragraph = Paragraph::new(Text::from(updates)) + .block(block) + .scroll((app.data.radarr_data.updates.offset, 0)); + + f.render_widget(updates_paragraph, area); + } else { + loading(f, block, area, app.is_loading); + } +}