Every size cell converted bytes to gigabytes and formatted the float in place, so anything below a gigabyte rendered as 0.00 GB. Books and tracks are almost always that small, which made the Size columns in Readarr and Lidarr useless. Replace convert_to_gb and convert_f64_to_gb with one format_size helper that renders gigabytes at or above 1 GiB and megabytes below it, at the precision each site already used. Every render site across the four servarr trees now calls it, including the two movie and episode detail strings built on the network side. Values at or above a gigabyte render byte for byte as before. Give the Readarr book statistics and book file fixtures and the Lidarr album statistics fixture realistic sub-gigabyte sizes so the megabyte path is pinned by the committed snapshots.
237 lines
7.1 KiB
Rust
237 lines
7.1 KiB
Rust
#[cfg(test)]
|
|
use crate::ui::ui_test_utils::test_utils::Utc;
|
|
use chrono::Duration;
|
|
#[cfg(not(test))]
|
|
use chrono::Utc;
|
|
use history::HistoryUi;
|
|
use library::LibraryUi;
|
|
use ratatui::{
|
|
Frame,
|
|
layout::{Constraint, Layout, Rect},
|
|
style::Stylize,
|
|
text::Text,
|
|
widgets::Paragraph,
|
|
};
|
|
use std::{cmp, iter};
|
|
|
|
use super::{
|
|
DrawUi, draw_tabs,
|
|
styles::ManagarrStyle,
|
|
utils::{
|
|
borderless_block, layout_block, line_gauge_with_label, line_gauge_with_title, title_block,
|
|
},
|
|
widgets::loading_block::LoadingBlock,
|
|
};
|
|
use crate::ui::readarr_ui::blocklist::BlocklistUi;
|
|
use crate::ui::readarr_ui::downloads::DownloadsUi;
|
|
use crate::ui::readarr_ui::indexers::IndexersUi;
|
|
use crate::ui::readarr_ui::root_folders::RootFoldersUi;
|
|
use crate::ui::readarr_ui::system::SystemUi;
|
|
use crate::ui::utils::{extract_monitored_disk_space_vec, extract_monitored_root_folders};
|
|
use crate::{
|
|
app::App,
|
|
logos::READARR_LOGO,
|
|
models::{
|
|
Route,
|
|
readarr_models::DownloadRecord,
|
|
servarr_data::readarr::readarr_data::ReadarrData,
|
|
servarr_models::{DiskSpace, RootFolder},
|
|
},
|
|
utils::format_size,
|
|
};
|
|
|
|
mod blocklist;
|
|
mod downloads;
|
|
mod history;
|
|
mod indexers;
|
|
mod library;
|
|
mod readarr_ui_utils;
|
|
mod root_folders;
|
|
mod system;
|
|
|
|
#[cfg(test)]
|
|
#[path = "readarr_ui_tests.rs"]
|
|
mod readarr_ui_tests;
|
|
|
|
pub(super) struct ReadarrUi;
|
|
|
|
impl DrawUi for ReadarrUi {
|
|
fn accepts(route: Route) -> bool {
|
|
matches!(route, Route::Readarr(_, _))
|
|
}
|
|
|
|
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|
let content_area = draw_tabs(f, area, "Authors", &app.data.readarr_data.main_tabs);
|
|
let route = app.get_current_route();
|
|
|
|
match route {
|
|
_ if LibraryUi::accepts(route) => LibraryUi::draw(f, app, content_area),
|
|
_ if DownloadsUi::accepts(route) => DownloadsUi::draw(f, app, content_area),
|
|
_ if BlocklistUi::accepts(route) => BlocklistUi::draw(f, app, content_area),
|
|
_ if HistoryUi::accepts(route) => HistoryUi::draw(f, app, content_area),
|
|
_ if RootFoldersUi::accepts(route) => RootFoldersUi::draw(f, app, content_area),
|
|
_ if IndexersUi::accepts(route) => IndexersUi::draw(f, app, content_area),
|
|
_ if SystemUi::accepts(route) => SystemUi::draw(f, app, content_area),
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
fn draw_context_row(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
|
|
let [main_area, logo_area] =
|
|
Layout::horizontal([Constraint::Fill(0), Constraint::Length(20)]).areas(area);
|
|
|
|
let [stats_area, downloads_area] =
|
|
Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]).areas(main_area);
|
|
|
|
draw_stats_context(f, app, stats_area);
|
|
draw_downloads_context(f, app, downloads_area);
|
|
draw_readarr_logo(f, logo_area);
|
|
}
|
|
}
|
|
|
|
fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
|
|
let block = title_block("Stats");
|
|
|
|
if !app.data.readarr_data.version.is_empty() {
|
|
f.render_widget(block, area);
|
|
let ReadarrData {
|
|
root_folders,
|
|
disk_space_vec,
|
|
start_time,
|
|
..
|
|
} = &app.data.readarr_data;
|
|
let monitored_disk_space_vec = extract_monitored_disk_space_vec(app, disk_space_vec.clone());
|
|
let monitored_root_folders = extract_monitored_root_folders(app, root_folders.items.clone());
|
|
|
|
let mut constraints = vec![
|
|
Constraint::Length(1),
|
|
Constraint::Length(1),
|
|
Constraint::Length(1),
|
|
];
|
|
|
|
constraints.append(
|
|
&mut iter::repeat_n(
|
|
Constraint::Length(1),
|
|
monitored_disk_space_vec.len() + monitored_root_folders.len() + 1,
|
|
)
|
|
.collect(),
|
|
);
|
|
|
|
let stat_item_areas = Layout::vertical(constraints).margin(1).split(area);
|
|
|
|
let version_paragraph = Paragraph::new(Text::from(format!(
|
|
"Readarr Version: {}",
|
|
app.data.readarr_data.version
|
|
)))
|
|
.block(borderless_block())
|
|
.bold();
|
|
|
|
let uptime = Utc::now() - start_time.to_owned();
|
|
let days = uptime.num_days();
|
|
let day_difference = uptime - Duration::days(days);
|
|
let hours = day_difference.num_hours();
|
|
let hour_difference = day_difference - Duration::hours(hours);
|
|
let minutes = hour_difference.num_minutes();
|
|
let seconds = (hour_difference - Duration::minutes(minutes)).num_seconds();
|
|
|
|
let uptime_paragraph = Paragraph::new(Text::from(format!(
|
|
"Uptime: {days}d {hours:0width$}:{minutes:0width$}:{seconds:0width$}",
|
|
width = 2
|
|
)))
|
|
.block(borderless_block())
|
|
.bold();
|
|
|
|
let storage = Paragraph::new(Text::from("Storage:")).block(borderless_block().bold());
|
|
let folders = Paragraph::new(Text::from("Root Folders:")).block(borderless_block().bold());
|
|
|
|
f.render_widget(version_paragraph, stat_item_areas[0]);
|
|
f.render_widget(uptime_paragraph, stat_item_areas[1]);
|
|
f.render_widget(storage, stat_item_areas[2]);
|
|
|
|
for i in 0..monitored_disk_space_vec.len() {
|
|
let DiskSpace {
|
|
path,
|
|
free_space,
|
|
total_space,
|
|
} = &monitored_disk_space_vec[i];
|
|
let title = if let Some(path) = path {
|
|
path
|
|
} else {
|
|
&format!("Disk {}", i + 1)
|
|
};
|
|
let ratio = if *total_space == 0 {
|
|
0f64
|
|
} else {
|
|
1f64 - (*free_space as f64 / *total_space as f64)
|
|
};
|
|
|
|
let space_gauge = line_gauge_with_label(title.as_str(), ratio);
|
|
|
|
f.render_widget(space_gauge, stat_item_areas[i + 3]);
|
|
}
|
|
|
|
f.render_widget(folders, stat_item_areas[monitored_disk_space_vec.len() + 3]);
|
|
|
|
for i in 0..monitored_root_folders.len() {
|
|
let RootFolder {
|
|
path, free_space, ..
|
|
} = &monitored_root_folders[i];
|
|
let space = format_size(*free_space, 2);
|
|
let root_folder_space = Paragraph::new(format!("{path}: {space} free"))
|
|
.block(borderless_block())
|
|
.default_color();
|
|
|
|
f.render_widget(
|
|
root_folder_space,
|
|
stat_item_areas[i + monitored_disk_space_vec.len() + 4],
|
|
)
|
|
}
|
|
} else {
|
|
f.render_widget(LoadingBlock::new(app.is_loading, block), area);
|
|
}
|
|
}
|
|
|
|
fn draw_downloads_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
|
|
let block = title_block("Downloads");
|
|
let downloads_vec = &app.data.readarr_data.downloads.items;
|
|
|
|
if !downloads_vec.is_empty() {
|
|
f.render_widget(block, area);
|
|
|
|
let max_items = ((((area.height as f64 / 2.0).floor() * 2.0) as i64) / 2) - 1;
|
|
let items = cmp::min(downloads_vec.len(), max_items.unsigned_abs() as usize);
|
|
let download_item_areas =
|
|
Layout::vertical(iter::repeat_n(Constraint::Length(2), items).collect::<Vec<Constraint>>())
|
|
.margin(1)
|
|
.split(area);
|
|
|
|
for i in 0..items {
|
|
let DownloadRecord {
|
|
title,
|
|
sizeleft,
|
|
size,
|
|
..
|
|
} = &downloads_vec[i];
|
|
let percent = if *size == 0.0 {
|
|
0.0
|
|
} else {
|
|
1f64 - (*sizeleft / *size)
|
|
};
|
|
let download_gauge = line_gauge_with_title(title, percent);
|
|
|
|
f.render_widget(download_gauge, download_item_areas[i]);
|
|
}
|
|
} else {
|
|
f.render_widget(LoadingBlock::new(app.is_loading, block), area);
|
|
}
|
|
}
|
|
|
|
fn draw_readarr_logo(f: &mut Frame<'_>, area: Rect) {
|
|
let logo_text = Text::from(READARR_LOGO);
|
|
let logo = Paragraph::new(logo_text)
|
|
.light_magenta()
|
|
.block(layout_block().default_color())
|
|
.centered();
|
|
f.render_widget(logo, area);
|
|
}
|