feat: Downloads tab support in Lidarr

This commit is contained in:
2026-01-13 13:40:18 -07:00
parent e1a25bfaf2
commit c68cd75015
32 changed files with 1551 additions and 165 deletions
@@ -0,0 +1,72 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::App;
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, DOWNLOADS_BLOCKS};
use crate::ui::DrawUi;
use crate::ui::lidarr_ui::downloads::DownloadsUi;
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
#[test]
fn test_downloads_ui_accepts() {
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
if DOWNLOADS_BLOCKS.contains(&active_lidarr_block) {
assert!(DownloadsUi::accepts(active_lidarr_block.into()));
} else {
assert!(!DownloadsUi::accepts(active_lidarr_block.into()));
}
});
}
mod snapshot_tests {
use crate::ui::ui_test_utils::test_utils::TerminalSize;
use rstest::rstest;
use super::*;
#[test]
fn test_downloads_ui_renders_loading() {
let mut app = App::test_default();
app.is_loading = true;
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
DownloadsUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_downloads_ui_renders_empty_downloads() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
DownloadsUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[rstest]
fn test_downloads_ui_renders(
#[values(
ActiveLidarrBlock::Downloads,
ActiveLidarrBlock::DeleteDownloadPrompt,
ActiveLidarrBlock::UpdateDownloadsPrompt
)]
active_lidarr_block: ActiveLidarrBlock,
) {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(active_lidarr_block.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
DownloadsUi::draw(f, app, f.area());
});
insta::assert_snapshot!(format!("downloads_ui_{active_lidarr_block}"), output);
}
}
}
+146
View File
@@ -0,0 +1,146 @@
use ratatui::Frame;
use ratatui::layout::{Constraint, Rect};
use ratatui::widgets::{Cell, Row};
use crate::app::App;
use crate::models::lidarr_models::DownloadRecord;
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, DOWNLOADS_BLOCKS};
use crate::models::{HorizontallyScrollableText, Route};
use crate::ui::DrawUi;
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::popup::{Popup, Size};
use crate::utils::convert_f64_to_gb;
#[cfg(test)]
#[path = "downloads_ui_tests.rs"]
mod downloads_ui_tests;
pub(super) struct DownloadsUi;
impl DrawUi for DownloadsUi {
fn accepts(route: Route) -> bool {
if let Route::Lidarr(active_lidarr_block, _) = route {
return DOWNLOADS_BLOCKS.contains(&active_lidarr_block);
}
false
}
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
if let Route::Lidarr(active_lidarr_block, _) = app.get_current_route() {
draw_downloads(f, app, area);
match active_lidarr_block {
ActiveLidarrBlock::DeleteDownloadPrompt => {
let prompt = format!(
"Do you really want to delete this download: \n{}?",
app.data.lidarr_data.downloads.current_selection().title
);
let confirmation_prompt = ConfirmationPrompt::new()
.title("Cancel Download")
.prompt(&prompt)
.yes_no_value(app.data.lidarr_data.prompt_confirm);
f.render_widget(
Popup::new(confirmation_prompt).size(Size::MediumPrompt),
f.area(),
);
}
ActiveLidarrBlock::UpdateDownloadsPrompt => {
let confirmation_prompt = ConfirmationPrompt::new()
.title("Update Downloads")
.prompt("Do you want to update your downloads?")
.yes_no_value(app.data.lidarr_data.prompt_confirm);
f.render_widget(
Popup::new(confirmation_prompt).size(Size::MediumPrompt),
f.area(),
);
}
_ => (),
}
}
}
}
fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let current_selection = if app.data.lidarr_data.downloads.items.is_empty() {
DownloadRecord::default()
} else {
app.data.lidarr_data.downloads.current_selection().clone()
};
let downloads_row_mapping = |download_record: &DownloadRecord| {
let DownloadRecord {
title,
size,
sizeleft,
download_client,
indexer,
output_path,
..
} = download_record;
if output_path.is_some() {
output_path.as_ref().unwrap().scroll_left_or_reset(
get_width_from_percentage(area, 18),
current_selection == *download_record,
app.ui_scroll_tick_count == 0,
);
}
let percent = if *size == 0.0 {
0.0
} else {
1f64 - (*sizeleft / *size)
};
let file_size: f64 = convert_f64_to_gb(*size);
Row::new(vec![
Cell::from(title.to_owned()),
Cell::from(format!("{:.0}%", percent * 100.0)),
Cell::from(format!("{file_size:.2} GB")),
Cell::from(
output_path
.as_ref()
.unwrap_or(&HorizontallyScrollableText::default())
.to_string(),
),
Cell::from(indexer.to_owned()),
Cell::from(
download_client
.as_ref()
.unwrap_or(&String::new())
.to_owned(),
),
])
.primary()
};
let downloads_table = ManagarrTable::new(
Some(&mut app.data.lidarr_data.downloads),
downloads_row_mapping,
)
.block(layout_block_top_border())
.loading(app.is_loading)
.headers([
"Title",
"Percent Complete",
"Size",
"Output Path",
"Indexer",
"Download Client",
])
.constraints([
Constraint::Percentage(30),
Constraint::Percentage(11),
Constraint::Percentage(11),
Constraint::Percentage(18),
Constraint::Percentage(17),
Constraint::Percentage(13),
]);
f.render_widget(downloads_table, area);
}
@@ -0,0 +1,38 @@
---
source: src/ui/lidarr_ui/downloads/downloads_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Title Percent Complete Size Output Path Indexer Download Client
=> Test download title 50% 3.30 GB /nfs/music/alex/album kickass torrents transmission
╭──────────────────── Cancel Download ────────────────────╮
│ Do you really want to delete this download: │
│ Test download title? │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│╭────────────────────────────╮╭───────────────────────────╮│
││ Yes ││ No ││
│╰────────────────────────────╯╰───────────────────────────╯│
╰───────────────────────────────────────────────────────────╯
@@ -0,0 +1,7 @@
---
source: src/ui/lidarr_ui/downloads/downloads_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Title Percent Complete Size Output Path Indexer Download Client
=> Test download title 50% 3.30 GB /nfs/music/alex/album kickass torrents transmission
@@ -0,0 +1,38 @@
---
source: src/ui/lidarr_ui/downloads/downloads_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Title Percent Complete Size Output Path Indexer Download Client
=> Test download title 50% 3.30 GB /nfs/music/alex/album kickass torrents transmission
╭─────────────────── Update Downloads ────────────────────╮
│ Do you want to update your downloads? │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│╭────────────────────────────╮╭───────────────────────────╮│
││ Yes ││ No ││
│╰────────────────────────────╯╰───────────────────────────╯│
╰───────────────────────────────────────────────────────────╯
@@ -0,0 +1,5 @@
---
source: src/ui/lidarr_ui/downloads/downloads_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
@@ -0,0 +1,8 @@
---
source: src/ui/lidarr_ui/downloads/downloads_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Loading ...
+26
View File
@@ -13,4 +13,30 @@ mod tests {
assert!(LidarrUi::accepts(Route::Lidarr(lidarr_block, None)));
}
}
mod snapshot_tests {
use super::*;
use crate::app::App;
use crate::ui::ui_test_utils::test_utils::{TerminalSize, render_to_string_with_app};
use rstest::rstest;
#[rstest]
#[case(ActiveLidarrBlock::Artists, 0)]
#[case(ActiveLidarrBlock::Downloads, 1)]
#[case(ActiveLidarrBlock::History, 2)]
fn test_lidarr_ui_renders_lidarr_tabs(
#[case] active_lidarr_block: ActiveLidarrBlock,
#[case] index: usize,
) {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(active_lidarr_block.into());
app.data.lidarr_data.main_tabs.set_index(index);
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
LidarrUi::draw(f, app, f.area());
});
insta::assert_snapshot!(format!("lidarr_tabs_{active_lidarr_block}"), output);
}
}
}
+11 -9
View File
@@ -15,6 +15,15 @@ use ratatui::{
widgets::Paragraph,
};
use super::{
DrawUi, draw_tabs,
styles::ManagarrStyle,
utils::{
borderless_block, layout_block, line_gauge_with_label, line_gauge_with_title, title_block,
},
widgets::loading_block::LoadingBlock,
};
use crate::ui::lidarr_ui::downloads::DownloadsUi;
use crate::{
app::App,
logos::LIDARR_LOGO,
@@ -27,15 +36,7 @@ use crate::{
utils::convert_to_gb,
};
use super::{
DrawUi, draw_tabs,
styles::ManagarrStyle,
utils::{
borderless_block, layout_block, line_gauge_with_label, line_gauge_with_title, title_block,
},
widgets::loading_block::LoadingBlock,
};
mod downloads;
mod history;
mod library;
mod lidarr_ui_utils;
@@ -57,6 +58,7 @@ impl DrawUi for LidarrUi {
match route {
_ if LibraryUi::accepts(route) => LibraryUi::draw(f, app, content_area),
_ if DownloadsUi::accepts(route) => DownloadsUi::draw(f, app, content_area),
_ if HistoryUi::accepts(route) => HistoryUi::draw(f, app, content_area),
_ => (),
}
@@ -0,0 +1,54 @@
---
source: src/ui/lidarr_ui/lidarr_ui_tests.rs
expression: output
---
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Downloads │ History │
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags │
│=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,54 @@
---
source: src/ui/lidarr_ui/lidarr_ui_tests.rs
expression: output
---
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Downloads │ History │
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Title Percent Complete Size Output Path Indexer Download Client │
│=> Test download title 50% 3.30 GB /nfs/music/alex/album kickass torrents transmission │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,54 @@
---
source: src/ui/lidarr_ui/lidarr_ui_tests.rs
expression: output
---
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Downloads │ History │
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Source Title ▼ Event Type Quality Date │
│=> Test source title grabbed Lossless 2023-01-01 00:00:00 UTC │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯