Updated Ratatui, created custom deserialization logic for i64s to make life easier, and used string interpolation where possible to reduce the lines needed to write log messages or create formatted text

This commit is contained in:
2023-09-07 17:20:38 -06:00
parent e13d1ece58
commit b16a58deae
43 changed files with 426 additions and 536 deletions
+3 -3
View File
@@ -17,7 +17,7 @@ backtrace = "0.3.67"
bimap = "0.6.3" bimap = "0.6.3"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
confy = { version = "0.5.1", default_features = false, features = ["yaml_conf"] } confy = { version = "0.5.1", default_features = false, features = ["yaml_conf"] }
crossterm = "0.26.1" crossterm = "0.27.0"
derivative = "2.2.0" derivative = "2.2.0"
human-panic = "1.1.3" human-panic = "1.1.3"
indoc = "2.0.0" indoc = "2.0.0"
@@ -32,14 +32,14 @@ strum = {version = "0.25.0", features = ["derive"] }
strum_macros = "0.25.0" strum_macros = "0.25.0"
tokio = { version = "1.29.0", features = ["full"] } tokio = { version = "1.29.0", features = ["full"] }
tokio-util = "0.7.8" tokio-util = "0.7.8"
tui = { version = "0.22.0", package = "ratatui", features = ["all-widgets"] } tui = { version = "0.23.0", package = "ratatui", features = ["all-widgets"] }
urlencoding = "2.1.2" urlencoding = "2.1.2"
[dev-dependencies] [dev-dependencies]
cargo-husky = { version = "1.5.0", default_features = false, features = ["user-hooks"] } cargo-husky = { version = "1.5.0", default_features = false, features = ["user-hooks"] }
mockito = "1.0.0" mockito = "1.0.0"
pretty_assertions = "1.3.0" pretty_assertions = "1.3.0"
rstest = "0.17.0" rstest = "0.18.2"
[[bin]] [[bin]]
bench = false bench = false
+1 -1
View File
@@ -18,7 +18,7 @@ run:
@CARGO_INCREMENTAL=1 cargo fmt && make lint && cargo run @CARGO_INCREMENTAL=1 cargo fmt && make lint && cargo run
lint: lint:
@find . | grep '\.\/src\/.*\.rs$$' | xargs touch && cargo clippy --all-targets --workspace ` @find . | grep '\.\/src\/.*\.rs$$' | xargs touch && CARGO_INCREMENTAL=0 cargo clippy --all-targets --workspace
lint-fix: lint-fix:
@cargo fix @cargo fix
+2
View File
@@ -0,0 +1,2 @@
[toolchain]
channel = "1.72.0"
+1 -1
View File
@@ -9,7 +9,7 @@ pub(in crate::app) type ContextClue = (KeyBinding, &'static str);
pub fn build_context_clue_string(context_clues: &[(KeyBinding, &str)]) -> String { pub fn build_context_clue_string(context_clues: &[(KeyBinding, &str)]) -> String {
context_clues context_clues
.iter() .iter()
.map(|(key_binding, desc)| format!("{} {}", key_binding.key, desc)) .map(|(key_binding, desc)| format!("{} {desc}", key_binding.key))
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(" | ") .join(" | ")
} }
+2 -2
View File
@@ -54,13 +54,13 @@ impl<'a> App<'a> {
} }
pub async fn dispatch_network_event(&mut self, action: NetworkEvent) { pub async fn dispatch_network_event(&mut self, action: NetworkEvent) {
debug!("Dispatching network event: {:?}", action); debug!("Dispatching network event: {action:?}");
self.is_loading = true; self.is_loading = true;
if let Some(network_tx) = &self.network_tx { if let Some(network_tx) = &self.network_tx {
if let Err(e) = network_tx.send(action).await { if let Err(e) = network_tx.send(action).await {
self.is_loading = false; self.is_loading = false;
error!("Failed to send event. {:?}", e); error!("Failed to send event. {e:?}");
self.handle_error(anyhow!(e)); self.handle_error(anyhow!(e));
} }
} }
+3 -3
View File
@@ -28,8 +28,8 @@ pub enum Key {
impl Display for Key { impl Display for Key {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self { match *self {
Key::Char(c) => write!(f, "<{}>", c), Key::Char(c) => write!(f, "<{c}>"),
Key::Ctrl(c) => write!(f, "<ctrl-{}>", c), Key::Ctrl(c) => write!(f, "<ctrl-{c}>"),
Key::Up => write!(f, "<↑>"), Key::Up => write!(f, "<↑>"),
Key::Down => write!(f, "<↓>"), Key::Down => write!(f, "<↓>"),
Key::Left => write!(f, "<←>"), Key::Left => write!(f, "<←>"),
@@ -41,7 +41,7 @@ impl Display for Key {
Key::End => write!(f, "<end>"), Key::End => write!(f, "<end>"),
Key::Tab => write!(f, "<tab>"), Key::Tab => write!(f, "<tab>"),
Key::Delete => write!(f, "<del>"), Key::Delete => write!(f, "<del>"),
_ => write!(f, "<{:?}>", self), _ => write!(f, "<{self:?}>"),
} }
} }
} }
+1 -1
View File
@@ -21,7 +21,7 @@ mod tests {
#[case(Key::Char('q'), "q")] #[case(Key::Char('q'), "q")]
#[case(Key::Ctrl('q'), "ctrl-q")] #[case(Key::Ctrl('q'), "ctrl-q")]
fn test_key_formatter(#[case] key: Key, #[case] expected_str: &str) { fn test_key_formatter(#[case] key: Key, #[case] expected_str: &str) {
assert_str_eq!(format!("{}", key), format!("<{}>", expected_str)); assert_str_eq!(format!("{key}"), format!("<{expected_str}>"));
} }
#[test] #[test]
@@ -83,8 +83,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHan
.radarr_data .radarr_data
.collection_movies .collection_movies
.current_selection() .current_selection()
.tmdb_id .tmdb_id;
.clone();
if self if self
.app .app
@@ -225,7 +225,7 @@ mod tests {
mod test_handle_key_char { mod test_handle_key_char {
use bimap::BiMap; use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use serde_json::Number;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::models::radarr_models::{Collection, MinimumAvailability}; use crate::models::radarr_models::{Collection, MinimumAvailability};
@@ -796,7 +796,7 @@ mod tests {
mod test_handle_key_char { mod test_handle_key_char {
use bimap::BiMap; use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use serde_json::Number;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::models::radarr_models::MinimumAvailability; use crate::models::radarr_models::MinimumAvailability;
@@ -336,8 +336,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
.as_ref() .as_ref()
.unwrap() .unwrap()
.current_selection() .current_selection()
.tmdb_id .tmdb_id;
.clone();
if self if self
.app .app
@@ -464,18 +464,11 @@ fn sort_releases_by_selected_field(
) -> Vec<Release> { ) -> Vec<Release> {
let cmp_fn: fn(&Release, &Release) -> Ordering = match field { let cmp_fn: fn(&Release, &Release) -> Ordering = match field {
ReleaseField::Source => |release_a, release_b| release_a.protocol.cmp(&release_b.protocol), ReleaseField::Source => |release_a, release_b| release_a.protocol.cmp(&release_b.protocol),
ReleaseField::Age => |release_a, release_b| release_a.age.as_u64().cmp(&release_b.age.as_u64()), ReleaseField::Age => |release_a, release_b| release_a.age.cmp(&release_b.age),
ReleaseField::Rejected => |release_a, release_b| release_a.rejected.cmp(&release_b.rejected), ReleaseField::Rejected => |release_a, release_b| release_a.rejected.cmp(&release_b.rejected),
ReleaseField::Title => |release_a, release_b| release_a.title.text.cmp(&release_b.title.text), ReleaseField::Title => |release_a, release_b| release_a.title.text.cmp(&release_b.title.text),
ReleaseField::Indexer => |release_a, release_b| release_a.indexer.cmp(&release_b.indexer), ReleaseField::Indexer => |release_a, release_b| release_a.indexer.cmp(&release_b.indexer),
ReleaseField::Size => |release_a, release_b| { ReleaseField::Size => |release_a, release_b| release_a.size.cmp(&release_b.size),
release_a
.size
.as_u64()
.as_ref()
.unwrap()
.cmp(release_b.size.as_u64().as_ref().unwrap())
},
ReleaseField::Peers => |release_a, release_b| { ReleaseField::Peers => |release_a, release_b| {
let default_number = Number::from(i64::MAX); let default_number = Number::from(i64::MAX);
let seeder_a = release_a let seeder_a = release_a
@@ -1112,10 +1112,10 @@ mod tests {
fn release_vec() -> Vec<Release> { fn release_vec() -> Vec<Release> {
let release_a = Release { let release_a = Release {
protocol: "Protocol A".to_owned(), protocol: "Protocol A".to_owned(),
age: Number::from(1), age: 1,
title: HorizontallyScrollableText::from("Title A"), title: HorizontallyScrollableText::from("Title A"),
indexer: "Indexer A".to_owned(), indexer: "Indexer A".to_owned(),
size: Number::from(1), size: 1,
rejected: true, rejected: true,
seeders: Some(Number::from(1)), seeders: Some(Number::from(1)),
languages: Some(vec![Language { languages: Some(vec![Language {
@@ -1130,10 +1130,10 @@ mod tests {
}; };
let release_b = Release { let release_b = Release {
protocol: "Protocol B".to_owned(), protocol: "Protocol B".to_owned(),
age: Number::from(2), age: 2,
title: HorizontallyScrollableText::from("Title B"), title: HorizontallyScrollableText::from("Title B"),
indexer: "Indexer B".to_owned(), indexer: "Indexer B".to_owned(),
size: Number::from(2), size: 2,
rejected: false, rejected: false,
seeders: Some(Number::from(2)), seeders: Some(Number::from(2)),
languages: Some(vec![Language { languages: Some(vec![Language {
@@ -1148,10 +1148,10 @@ mod tests {
}; };
let release_c = Release { let release_c = Release {
protocol: "Protocol C".to_owned(), protocol: "Protocol C".to_owned(),
age: Number::from(3), age: 3,
title: HorizontallyScrollableText::from("Title C"), title: HorizontallyScrollableText::from("Title C"),
indexer: "Indexer C".to_owned(), indexer: "Indexer C".to_owned(),
size: Number::from(3), size: 3,
rejected: false, rejected: false,
seeders: None, seeders: None,
languages: None, languages: None,
@@ -17,7 +17,7 @@ mod utils {
radarr_data.movies.set_items(vec![Movie { radarr_data.movies.set_items(vec![Movie {
path: "/nfs/movies/Test".to_owned().into(), path: "/nfs/movies/Test".to_owned().into(),
monitored: true, monitored: true,
quality_profile_id: Number::from(2222), quality_profile_id: 2222,
minimum_availability: MinimumAvailability::Released, minimum_availability: MinimumAvailability::Released,
tags: vec![Number::from(1)], tags: vec![Number::from(1)],
..Movie::default() ..Movie::default()
@@ -133,7 +133,7 @@ mod utils {
root_folder_path: "/nfs/movies/Test".to_owned().into(), root_folder_path: "/nfs/movies/Test".to_owned().into(),
monitored: true, monitored: true,
search_on_add: true, search_on_add: true,
quality_profile_id: Number::from(2222), quality_profile_id: 2222,
minimum_availability: MinimumAvailability::Released, minimum_availability: MinimumAvailability::Released,
..Collection::default() ..Collection::default()
}]); }]);
+1 -2
View File
@@ -132,8 +132,7 @@ fn panic_hook(info: &PanicInfo<'_>) {
io::stdout(), io::stdout(),
LeaveAlternateScreen, LeaveAlternateScreen,
Print(format!( Print(format!(
"thread '<unnamed>' panicked at '{}', {}\n\r{}", "thread '<unnamed>' panicked at '{msg}', {location}\n\r{stacktrace}"
msg, location, stacktrace
)), )),
) )
.unwrap(); .unwrap();
+12 -1
View File
@@ -2,7 +2,8 @@ use std::cell::RefCell;
use std::fmt::{Debug, Display, Formatter}; use std::fmt::{Debug, Display, Formatter};
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use serde::Deserialize; use serde::{de, Deserialize, Deserializer};
use serde_json::Number;
use tui::widgets::{ListState, TableState}; use tui::widgets::{ListState, TableState};
pub mod radarr_models; pub mod radarr_models;
@@ -409,3 +410,13 @@ where
self.index = index; self.index = index;
} }
} }
pub fn from_i64<'de, D>(deserializer: D) -> Result<i64, D::Error>
where
D: Deserializer<'de>,
{
let num: Number = Deserialize::deserialize(deserializer)?;
num.as_i64().ok_or(de::Error::custom(format!(
"Unable to convert Number to i64: {num:?}"
)))
}
+22
View File
@@ -3,7 +3,12 @@ mod tests {
use std::cell::RefCell; use std::cell::RefCell;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use serde::de::value::Error as ValueError;
use serde::de::value::F64Deserializer;
use serde::de::value::I64Deserializer;
use serde::de::IntoDeserializer;
use crate::models::from_i64;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::models::{ use crate::models::{
BlockSelectionState, HorizontallyScrollableText, Scrollable, ScrollableText, StatefulList, BlockSelectionState, HorizontallyScrollableText, Scrollable, ScrollableText, StatefulList,
@@ -715,6 +720,23 @@ mod tests {
assert_eq!(block_selection_state.get_active_block(), &blocks[0]); assert_eq!(block_selection_state.get_active_block(), &blocks[0]);
} }
#[test]
fn test_from_i64() {
let deserializer: I64Deserializer<ValueError> = 1i64.into_deserializer();
assert_eq!(from_i64(deserializer), Ok(1));
}
#[test]
fn test_from_i64_error() {
let deserializer: F64Deserializer<ValueError> = 1f64.into_deserializer();
assert_eq!(
from_i64(deserializer).unwrap_err().to_string(),
"Unable to convert Number to i64: Number(1.0)"
);
}
fn create_test_tab_routes() -> Vec<TabRoute> { fn create_test_tab_routes() -> Vec<TabRoute> {
vec![ vec![
TabRoute { TabRoute {
+113 -124
View File
@@ -15,31 +15,30 @@ mod radarr_models_tests;
#[derive(Default, Serialize, Debug)] #[derive(Default, Serialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct AddMovieBody { pub struct AddMovieBody {
pub tmdb_id: u64, pub tmdb_id: i64,
pub title: String, pub title: String,
pub root_folder_path: String, pub root_folder_path: String,
pub quality_profile_id: u64, pub quality_profile_id: i64,
pub minimum_availability: String, pub minimum_availability: String,
pub monitored: bool, pub monitored: bool,
pub tags: Vec<u64>, pub tags: Vec<i64>,
pub add_options: AddOptions, pub add_options: AddOptions,
} }
#[derive(Derivative, Deserialize, Debug, Clone, PartialEq, Eq)] #[derive(Derivative, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
#[derivative(Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct AddMovieSearchResult { pub struct AddMovieSearchResult {
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub tmdb_id: Number, pub tmdb_id: i64,
pub title: HorizontallyScrollableText, pub title: HorizontallyScrollableText,
pub original_language: Language, pub original_language: Language,
pub status: String, pub status: String,
pub overview: String, pub overview: String,
pub genres: Vec<String>, pub genres: Vec<String>,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub year: Number, pub year: i64,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub runtime: Number, pub runtime: i64,
pub ratings: RatingsList, pub ratings: RatingsList,
} }
@@ -55,12 +54,11 @@ pub struct AddRootFolderBody {
pub path: String, pub path: String,
} }
#[derive(Deserialize, Derivative, Clone, Debug, PartialEq, Eq)] #[derive(Deserialize, Derivative, Default, Clone, Debug, PartialEq, Eq)]
#[derivative(Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Collection { pub struct Collection {
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub id: Number, pub id: i64,
#[serde(default)] #[serde(default)]
pub title: HorizontallyScrollableText, pub title: HorizontallyScrollableText,
pub root_folder_path: Option<String>, pub root_folder_path: Option<String>,
@@ -68,28 +66,27 @@ pub struct Collection {
pub monitored: bool, pub monitored: bool,
pub overview: Option<String>, pub overview: Option<String>,
pub minimum_availability: MinimumAvailability, pub minimum_availability: MinimumAvailability,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub quality_profile_id: Number, pub quality_profile_id: i64,
pub movies: Option<Vec<CollectionMovie>>, pub movies: Option<Vec<CollectionMovie>>,
} }
#[derive(Derivative, Deserialize, Debug, Clone, PartialEq, Eq)] #[derive(Derivative, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
#[derivative(Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CollectionMovie { pub struct CollectionMovie {
pub title: HorizontallyScrollableText, pub title: HorizontallyScrollableText,
pub overview: String, pub overview: String,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub year: Number, pub year: i64,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub runtime: Number, pub runtime: i64,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub tmdb_id: Number, pub tmdb_id: i64,
pub genres: Vec<String>, pub genres: Vec<String>,
pub ratings: RatingsList, pub ratings: RatingsList,
} }
#[derive(Default, Derivative, Serialize, Debug)] #[derive(Default, Serialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CommandBody { pub struct CommandBody {
pub name: String, pub name: String,
@@ -117,42 +114,41 @@ pub enum CreditType {
#[derive(Deserialize, Debug, Clone, Eq, PartialEq)] #[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct DiskSpace { pub struct DiskSpace {
pub free_space: Number, #[serde(deserialize_with = "super::from_i64")]
pub total_space: Number, pub free_space: i64,
#[serde(deserialize_with = "super::from_i64")]
pub total_space: i64,
} }
#[derive(Derivative, Deserialize, Debug, Clone, PartialEq, Eq)] #[derive(Derivative, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
#[derivative(Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct DownloadRecord { pub struct DownloadRecord {
pub title: String, pub title: String,
pub status: String, pub status: String,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub id: Number, pub id: i64,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub movie_id: Number, pub movie_id: i64,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub size: Number, pub size: i64,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub sizeleft: Number, pub sizeleft: i64,
pub output_path: Option<HorizontallyScrollableText>, pub output_path: Option<HorizontallyScrollableText>,
pub indexer: String, pub indexer: String,
pub download_client: String, pub download_client: String,
} }
#[derive(Derivative, Deserialize, Debug)] #[derive(Default, Deserialize, Debug)]
#[derivative(Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct DownloadsResponse { pub struct DownloadsResponse {
pub records: Vec<DownloadRecord>, pub records: Vec<DownloadRecord>,
} }
#[derive(Derivative, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] #[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
#[derivative(Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Indexer { pub struct Indexer {
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub id: Number, pub id: i64,
pub name: Option<String>, pub name: Option<String>,
pub implementation: Option<String>, pub implementation: Option<String>,
pub implementation_name: Option<String>, pub implementation_name: Option<String>,
@@ -164,19 +160,18 @@ pub struct Indexer {
pub enable_automatic_search: bool, pub enable_automatic_search: bool,
pub enable_interactive_search: bool, pub enable_interactive_search: bool,
pub protocol: String, pub protocol: String,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub priority: Number, pub priority: i64,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub download_client_id: Number, pub download_client_id: i64,
pub tags: Option<Vec<String>>, pub tags: Option<Vec<String>>,
} }
#[derive(Derivative, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] #[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
#[derivative(Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct IndexerField { pub struct IndexerField {
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub order: Number, pub order: i64,
pub name: Option<String>, pub name: Option<String>,
pub label: Option<String>, pub label: Option<String>,
pub value: Option<Value>, pub value: Option<Value>,
@@ -185,35 +180,33 @@ pub struct IndexerField {
pub select_options: Option<Vec<IndexerSelectOption>>, pub select_options: Option<Vec<IndexerSelectOption>>,
} }
#[derive(Derivative, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] #[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
#[derivative(Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct IndexerSelectOption { pub struct IndexerSelectOption {
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub value: Number, pub value: i64,
pub name: Option<String>, pub name: Option<String>,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub order: Number, pub order: i64,
} }
#[derive(Derivative, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] #[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
#[derivative(Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct IndexerSettings { pub struct IndexerSettings {
pub allow_hardcoded_subs: bool, pub allow_hardcoded_subs: bool,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub availability_delay: Number, pub availability_delay: i64,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub id: Number, pub id: i64,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub maximum_size: Number, pub maximum_size: i64,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub minimum_age: Number, pub minimum_age: i64,
pub prefer_indexer_flags: bool, pub prefer_indexer_flags: bool,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub retention: Number, pub retention: i64,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub rss_sync_interval: Number, pub rss_sync_interval: i64,
pub whitelisted_hardcoded_subs: String, pub whitelisted_hardcoded_subs: String,
} }
@@ -243,18 +236,18 @@ pub struct LogResponse {
#[derivative(Default)] #[derivative(Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct MediaInfo { pub struct MediaInfo {
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub audio_bitrate: Number, pub audio_bitrate: i64,
#[derivative(Default(value = "Number::from(0)"))] #[derivative(Default(value = "Number::from(0)"))]
pub audio_channels: Number, pub audio_channels: Number,
pub audio_codec: Option<String>, pub audio_codec: Option<String>,
pub audio_languages: Option<String>, pub audio_languages: Option<String>,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub audio_stream_count: Number, pub audio_stream_count: i64,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub video_bit_depth: Number, pub video_bit_depth: i64,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub video_bitrate: Number, pub video_bitrate: i64,
pub video_codec: String, pub video_codec: String,
#[derivative(Default(value = "Number::from(0)"))] #[derivative(Default(value = "Number::from(0)"))]
pub video_fps: Number, pub video_fps: Number,
@@ -281,7 +274,7 @@ impl Display for MinimumAvailability {
MinimumAvailability::InCinemas => "inCinemas", MinimumAvailability::InCinemas => "inCinemas",
MinimumAvailability::Released => "released", MinimumAvailability::Released => "released",
}; };
write!(f, "{}", minimum_availability) write!(f, "{minimum_availability}")
} }
} }
@@ -311,7 +304,7 @@ impl Display for Monitor {
Monitor::MovieAndCollection => "movieAndCollection", Monitor::MovieAndCollection => "movieAndCollection",
Monitor::None => "none", Monitor::None => "none",
}; };
write!(f, "{}", monitor) write!(f, "{monitor}")
} }
} }
@@ -329,27 +322,27 @@ impl Monitor {
#[derivative(Default)] #[derivative(Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Movie { pub struct Movie {
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub id: Number, pub id: i64,
pub title: HorizontallyScrollableText, pub title: HorizontallyScrollableText,
pub original_language: Language, pub original_language: Language,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub size_on_disk: Number, pub size_on_disk: i64,
pub status: String, pub status: String,
pub overview: String, pub overview: String,
pub path: String, pub path: String,
pub studio: String, pub studio: String,
pub genres: Vec<String>, pub genres: Vec<String>,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub year: Number, pub year: i64,
pub monitored: bool, pub monitored: bool,
pub has_file: bool, pub has_file: bool,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub runtime: Number, pub runtime: i64,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub tmdb_id: Number, pub tmdb_id: i64,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub quality_profile_id: Number, pub quality_profile_id: i64,
pub minimum_availability: MinimumAvailability, pub minimum_availability: MinimumAvailability,
pub certification: Option<String>, pub certification: Option<String>,
pub tags: Vec<Number>, pub tags: Vec<Number>,
@@ -358,11 +351,11 @@ pub struct Movie {
pub collection: Option<Collection>, pub collection: Option<Collection>,
} }
#[derive(Default, Derivative, Serialize, Debug)] #[derive(Default, Serialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct MovieCommandBody { pub struct MovieCommandBody {
pub name: String, pub name: String,
pub movie_ids: Vec<u64>, pub movie_ids: Vec<i64>,
} }
#[derive(Deserialize, Derivative, Debug, Clone, PartialEq, Eq)] #[derive(Deserialize, Derivative, Debug, Clone, PartialEq, Eq)]
@@ -390,11 +383,10 @@ pub struct Quality {
pub name: String, pub name: String,
} }
#[derive(Derivative, Deserialize, Debug)] #[derive(Default, Deserialize, Debug)]
#[derivative(Default)]
pub struct QualityProfile { pub struct QualityProfile {
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub id: Number, pub id: i64,
pub name: String, pub name: String,
} }
@@ -431,20 +423,20 @@ pub struct RatingsList {
pub rotten_tomatoes: Option<Rating>, pub rotten_tomatoes: Option<Rating>,
} }
#[derive(Deserialize, Derivative, Clone, Debug, PartialEq, Eq)] #[derive(Deserialize, Default, Clone, Debug, PartialEq, Eq)]
#[derivative(Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[serde(default)]
pub struct Release { pub struct Release {
pub guid: String, pub guid: String,
pub protocol: String, pub protocol: String,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub age: Number, pub age: i64,
pub title: HorizontallyScrollableText, pub title: HorizontallyScrollableText,
pub indexer: String, pub indexer: String,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub indexer_id: Number, pub indexer_id: i64,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub size: Number, pub size: i64,
pub rejected: bool, pub rejected: bool,
pub rejections: Option<Vec<String>>, pub rejections: Option<Vec<String>>,
pub seeders: Option<Number>, pub seeders: Option<Number>,
@@ -457,8 +449,8 @@ pub struct Release {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ReleaseDownloadBody { pub struct ReleaseDownloadBody {
pub guid: String, pub guid: String,
pub indexer_id: u64, pub indexer_id: i64,
pub movie_id: u64, pub movie_id: i64,
} }
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter, Display)] #[derive(Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter, Display)]
@@ -475,16 +467,15 @@ pub enum ReleaseField {
Quality, Quality,
} }
#[derive(Derivative, Deserialize, Debug, Clone, Eq, PartialEq)] #[derive(Default, Deserialize, Debug, Clone, Eq, PartialEq)]
#[derivative(Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RootFolder { pub struct RootFolder {
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub id: Number, pub id: i64,
pub path: String, pub path: String,
pub accessible: bool, pub accessible: bool,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub free_space: Number, pub free_space: i64,
pub unmapped_folders: Option<Vec<UnmappedFolder>>, pub unmapped_folders: Option<Vec<UnmappedFolder>>,
} }
@@ -495,22 +486,20 @@ pub struct SystemStatus {
pub start_time: DateTime<Utc>, pub start_time: DateTime<Utc>,
} }
#[derive(Derivative, Deserialize, Debug)] #[derive(Default, Deserialize, Debug)]
#[derivative(Default)]
pub struct Tag { pub struct Tag {
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub id: Number, pub id: i64,
pub label: String, pub label: String,
} }
#[derive(Derivative, Deserialize, Debug, Clone, PartialEq, Eq)] #[derive(Default, Deserialize, Debug, Clone, PartialEq, Eq)]
#[derivative(Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Task { pub struct Task {
pub name: String, pub name: String,
pub task_name: String, pub task_name: String,
#[derivative(Default(value = "Number::from(0)"))] #[serde(deserialize_with = "super::from_i64")]
pub interval: Number, pub interval: i64,
pub last_execution: DateTime<Utc>, pub last_execution: DateTime<Utc>,
pub last_duration: String, pub last_duration: String,
pub next_execution: DateTime<Utc>, pub next_execution: DateTime<Utc>,
+3 -3
View File
@@ -58,7 +58,7 @@ impl From<&RadarrData<'_>> for EditMovieModal {
.map(|tag_id| { .map(|tag_id| {
radarr_data radarr_data
.tags_map .tags_map
.get_by_left(&tag_id.as_u64().unwrap()) .get_by_left(&tag_id.as_i64().unwrap())
.unwrap() .unwrap()
.clone() .clone()
}) })
@@ -89,7 +89,7 @@ impl From<&RadarrData<'_>> for EditMovieModal {
.set_items(quality_profile_names); .set_items(quality_profile_names);
let quality_profile_name = radarr_data let quality_profile_name = radarr_data
.quality_profile_map .quality_profile_map
.get_by_left(&quality_profile_id.as_u64().unwrap()) .get_by_left(quality_profile_id)
.unwrap(); .unwrap();
let quality_profile_index = edit_movie_modal let quality_profile_index = edit_movie_modal
.quality_profile_list .quality_profile_list
@@ -193,7 +193,7 @@ impl From<&RadarrData<'_>> for EditCollectionModal {
let quality_profile_name = radarr_data let quality_profile_name = radarr_data
.quality_profile_map .quality_profile_map
.get_by_left(&quality_profile_id.as_u64().unwrap()) .get_by_left(quality_profile_id)
.unwrap(); .unwrap();
let quality_profile_index = edit_collection_modal let quality_profile_index = edit_collection_modal
.quality_profile_list .quality_profile_list
@@ -27,7 +27,7 @@ mod test {
let movie = Movie { let movie = Movie {
path: "/nfs/movies/Test".to_owned(), path: "/nfs/movies/Test".to_owned(),
monitored: true, monitored: true,
quality_profile_id: Number::from(2222), quality_profile_id: 2222,
minimum_availability: MinimumAvailability::Released, minimum_availability: MinimumAvailability::Released,
tags: vec![Number::from(1), Number::from(2)], tags: vec![Number::from(1), Number::from(2)],
..Movie::default() ..Movie::default()
@@ -69,10 +69,10 @@ mod test {
#[test] #[test]
fn test_add_movie_modal_from_radarr_data() { fn test_add_movie_modal_from_radarr_data() {
let root_folder = RootFolder { let root_folder = RootFolder {
id: Number::from(1), id: 1,
path: "/nfs".to_owned(), path: "/nfs".to_owned(),
accessible: true, accessible: true,
free_space: Number::from(219902325555200u64), free_space: 219902325555200,
unmapped_folders: None, unmapped_folders: None,
}; };
let mut radarr_data = RadarrData { let mut radarr_data = RadarrData {
@@ -120,7 +120,7 @@ mod test {
root_folder_path: Some("/nfs/movies/Test".to_owned()), root_folder_path: Some("/nfs/movies/Test".to_owned()),
monitored: true, monitored: true,
search_on_add: true, search_on_add: true,
quality_profile_id: Number::from(2222), quality_profile_id: 2222,
minimum_availability: MinimumAvailability::Released, minimum_availability: MinimumAvailability::Released,
..Collection::default() ..Collection::default()
}; };
@@ -38,8 +38,8 @@ 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 quality_profile_map: BiMap<u64, String>, pub quality_profile_map: BiMap<i64, String>,
pub tags_map: BiMap<u64, String>, pub tags_map: BiMap<i64, String>,
pub collections: StatefulTable<Collection>, pub collections: StatefulTable<Collection>,
pub collection_movies: StatefulTable<CollectionMovie>, pub collection_movies: StatefulTable<CollectionMovie>,
pub logs: StatefulList<HorizontallyScrollableText>, pub logs: StatefulList<HorizontallyScrollableText>,
+9 -19
View File
@@ -63,7 +63,7 @@ impl<'a, 'b> Network<'a, 'b> {
let request_uri = request_props.uri.clone(); let request_uri = request_props.uri.clone();
select! { select! {
_ = self.cancellation_token.cancelled() => { _ = self.cancellation_token.cancelled() => {
warn!("Received Cancel request. Cancelling request to: {}", request_uri); warn!("Received Cancel request. Cancelling request to: {request_uri}");
let mut app = self.app.lock().await; let mut app = self.app.lock().await;
self.cancellation_token = app.reset_cancellation_token(); self.cancellation_token = app.reset_cancellation_token();
app.is_loading = false; app.is_loading = false;
@@ -80,12 +80,12 @@ impl<'a, 'b> Network<'a, 'b> {
app_update_fn(value, app); app_update_fn(value, app);
} }
Err(e) => { Err(e) => {
error!("Failed to parse response! {:?}", e); error!("Failed to parse response! {e:?}");
self self
.app .app
.lock() .lock()
.await .await
.handle_error(anyhow!("Failed to parse response! {:?}", e)); .handle_error(anyhow!("Failed to parse response! {e:?}"));
} }
} }
} }
@@ -99,24 +99,17 @@ impl<'a, 'b> Network<'a, 'b> {
.replace_all(&response_body.replace('\n', " "), " ") .replace_all(&response_body.replace('\n', " "), " ")
.to_string(); .to_string();
error!( error!("Request failed. Received {status} response code with body: {response_body}");
"Request failed. Received {} response code with body: {}", self.app.lock().await.handle_error(anyhow!("Request failed. Received {status} response code with body: {error_body}"));
status, response_body
);
self.app.lock().await.handle_error(anyhow!(
"Request failed. Received {} response code with body: {}",
status,
error_body
));
} }
} }
Err(e) => { Err(e) => {
error!("Failed to send request. {:?}", e); error!("Failed to send request. {e:?}");
self self
.app .app
.lock() .lock()
.await .await
.handle_error(anyhow!("Failed to send request. {} ", e)); .handle_error(anyhow!("Failed to send request. {e} "));
} }
} }
} }
@@ -133,11 +126,8 @@ impl<'a, 'b> Network<'a, 'b> {
body, body,
api_token, api_token,
} = request_props; } = request_props;
debug!("Creating RequestBuilder for resource: {:?}", uri); debug!("Creating RequestBuilder for resource: {uri:?}");
debug!( debug!("Sending {method:?} request to {uri} with body {body:?}");
"Sending {:?} request to {} with body {:?}",
method, uri, body
);
match method { match method {
RequestMethod::Get => self.client.get(uri).header("X-Api-Key", api_token), RequestMethod::Get => self.client.get(uri).header("X-Api-Key", api_token),
+83 -185
View File
@@ -4,7 +4,7 @@ use std::fmt::Debug;
use indoc::formatdoc; use indoc::formatdoc;
use log::{debug, info, warn}; use log::{debug, info, warn};
use serde::Serialize; use serde::Serialize;
use serde_json::{json, Number, Value}; use serde_json::{json, Value};
use urlencoding::encode; use urlencoding::encode;
use crate::app::RadarrConfig; use crate::app::RadarrConfig;
@@ -226,7 +226,7 @@ impl<'a, 'b> Network<'a, 'b> {
app.data.radarr_data.add_movie_modal = None; app.data.radarr_data.add_movie_modal = None;
AddMovieBody { AddMovieBody {
tmdb_id: tmdb_id.as_u64().unwrap(), tmdb_id,
title, title,
root_folder_path: path, root_folder_path: path,
minimum_availability, minimum_availability,
@@ -240,7 +240,7 @@ impl<'a, 'b> Network<'a, 'b> {
} }
}; };
debug!("Add movie body: {:?}", body); debug!("Add movie body: {body:?}");
let request_props = self let request_props = self
.radarr_request_props_from( .radarr_request_props_from(
@@ -273,7 +273,7 @@ impl<'a, 'b> Network<'a, 'b> {
AddRootFolderBody { path } AddRootFolderBody { path }
}; };
debug!("Add root folder body: {:?}", body); debug!("Add root folder body: {body:?}");
let request_props = self let request_props = self
.radarr_request_props_from( .radarr_request_props_from(
@@ -301,11 +301,7 @@ impl<'a, 'b> Network<'a, 'b> {
self self
.handle_request::<Value, Tag>(request_props, |tag, mut app| { .handle_request::<Value, Tag>(request_props, |tag, mut app| {
app app.data.radarr_data.tags_map.insert(tag.id, tag.label);
.data
.radarr_data
.tags_map
.insert(tag.id.as_u64().unwrap(), tag.label);
}) })
.await; .await;
} }
@@ -319,18 +315,13 @@ impl<'a, 'b> Network<'a, 'b> {
.radarr_data .radarr_data
.downloads .downloads
.current_selection() .current_selection()
.id .id;
.as_u64()
.unwrap();
info!( info!("Deleting Radarr download for download with id: {download_id}");
"Deleting Radarr download for download with id: {}",
download_id
);
let request_props = self let request_props = self
.radarr_request_props_from( .radarr_request_props_from(
format!("{}/{}", RadarrEvent::DeleteDownload.resource(), download_id).as_str(), format!("{}/{download_id}", RadarrEvent::DeleteDownload.resource()).as_str(),
RequestMethod::Delete, RequestMethod::Delete,
None::<()>, None::<()>,
) )
@@ -350,18 +341,13 @@ impl<'a, 'b> Network<'a, 'b> {
.radarr_data .radarr_data
.indexers .indexers
.current_selection() .current_selection()
.id .id;
.as_u64()
.unwrap();
info!( info!("Deleting Radarr indexer for indexer with id: {indexer_id}");
"Deleting Radarr indexer for indexer with id: {}",
indexer_id
);
let request_props = self let request_props = self
.radarr_request_props_from( .radarr_request_props_from(
format!("{}/{}", RadarrEvent::DeleteIndexer.resource(), indexer_id).as_str(), format!("{}/{indexer_id}", RadarrEvent::DeleteIndexer.resource()).as_str(),
RequestMethod::Delete, RequestMethod::Delete,
None::<()>, None::<()>,
) )
@@ -377,19 +363,13 @@ impl<'a, 'b> Network<'a, 'b> {
let delete_files = self.app.lock().await.data.radarr_data.delete_movie_files; let delete_files = self.app.lock().await.data.radarr_data.delete_movie_files;
let add_import_exclusion = self.app.lock().await.data.radarr_data.add_list_exclusion; let add_import_exclusion = self.app.lock().await.data.radarr_data.add_list_exclusion;
info!( info!("Deleting Radarr movie with tmdb_id {tmdb_id} and Radarr id: {movie_id} with deleteFiles={delete_files} and addImportExclusion={add_import_exclusion}");
"Deleting Radarr movie with tmdb_id {} and Radarr id: {} with deleteFiles={} and addImportExclusion={}",
tmdb_id, movie_id, delete_files, add_import_exclusion
);
let request_props = self let request_props = self
.radarr_request_props_from( .radarr_request_props_from(
format!( format!(
"{}/{}?deleteFiles={}&addImportExclusion={}", "{}/{movie_id}?deleteFiles={delete_files}&addImportExclusion={add_import_exclusion}",
RadarrEvent::DeleteMovie.resource(), RadarrEvent::DeleteMovie.resource()
movie_id,
delete_files,
add_import_exclusion
) )
.as_str(), .as_str(),
RequestMethod::Delete, RequestMethod::Delete,
@@ -419,21 +399,15 @@ impl<'a, 'b> Network<'a, 'b> {
.radarr_data .radarr_data
.root_folders .root_folders
.current_selection() .current_selection()
.id .id;
.as_u64()
.unwrap();
info!( info!("Deleting Radarr root folder for folder with id: {root_folder_id}");
"Deleting Radarr root folder for folder with id: {}",
root_folder_id
);
let request_props = self let request_props = self
.radarr_request_props_from( .radarr_request_props_from(
format!( format!(
"{}/{}", "{}/{root_folder_id}",
RadarrEvent::DeleteRootFolder.resource(), RadarrEvent::DeleteRootFolder.resource()
root_folder_id
) )
.as_str(), .as_str(),
RequestMethod::Delete, RequestMethod::Delete,
@@ -464,10 +438,10 @@ impl<'a, 'b> Network<'a, 'b> {
.movie_releases .movie_releases
.current_selection(); .current_selection();
(guid.clone(), title.clone(), indexer_id.as_u64().unwrap()) (guid.clone(), title.clone(), *indexer_id)
}; };
info!("Downloading release: {}", title); info!("Downloading release: {title}");
let download_release_body = ReleaseDownloadBody { let download_release_body = ReleaseDownloadBody {
guid, guid,
@@ -495,12 +469,7 @@ impl<'a, 'b> Network<'a, 'b> {
let collection_id = self.extract_collection_id().await; let collection_id = self.extract_collection_id().await;
let request_props = self let request_props = self
.radarr_request_props_from( .radarr_request_props_from(
format!( format!("{}/{collection_id}", RadarrEvent::GetCollections.resource()).as_str(),
"{}/{}",
RadarrEvent::GetCollections.resource(),
collection_id
)
.as_str(),
RequestMethod::Get, RequestMethod::Get,
None::<()>, None::<()>,
) )
@@ -557,16 +526,11 @@ impl<'a, 'b> Network<'a, 'b> {
detailed_collection_body detailed_collection_body
}; };
debug!("Edit collection body: {:?}", body); debug!("Edit collection body: {body:?}");
let request_props = self let request_props = self
.radarr_request_props_from( .radarr_request_props_from(
format!( format!("{}/{collection_id}", RadarrEvent::EditCollection.resource()).as_str(),
"{}/{}",
RadarrEvent::EditCollection.resource(),
collection_id
)
.as_str(),
RequestMethod::Put, RequestMethod::Put,
Some(body), Some(body),
) )
@@ -585,7 +549,7 @@ impl<'a, 'b> Network<'a, 'b> {
let request_props = self let request_props = self
.radarr_request_props_from( .radarr_request_props_from(
format!("{}/{}", RadarrEvent::GetMovieDetails.resource(), movie_id).as_str(), format!("{}/{movie_id}", RadarrEvent::GetMovieDetails.resource()).as_str(),
RequestMethod::Get, RequestMethod::Get,
None::<()>, None::<()>,
) )
@@ -648,11 +612,11 @@ impl<'a, 'b> Network<'a, 'b> {
detailed_movie_body detailed_movie_body
}; };
debug!("Edit movie body: {:?}", body); debug!("Edit movie body: {body:?}");
let request_props = self let request_props = self
.radarr_request_props_from( .radarr_request_props_from(
format!("{}/{}", RadarrEvent::EditMovie.resource(), movie_id).as_str(), format!("{}/{movie_id}", RadarrEvent::EditMovie.resource()).as_str(),
RequestMethod::Put, RequestMethod::Put,
Some(body), Some(body),
) )
@@ -858,7 +822,7 @@ impl<'a, 'b> Network<'a, 'b> {
let request_props = self let request_props = self
.radarr_request_props_from( .radarr_request_props_from(
format!("{}/{}", RadarrEvent::GetMovieDetails.resource(), movie_id).as_str(), format!("{}/{movie_id}", RadarrEvent::GetMovieDetails.resource()).as_str(),
RequestMethod::Get, RequestMethod::Get,
None::<()>, None::<()>,
) )
@@ -884,43 +848,43 @@ impl<'a, 'b> Network<'a, 'b> {
collection, collection,
.. ..
} = movie_response; } = movie_response;
let (hours, minutes) = convert_runtime(runtime.as_u64().unwrap()); let (hours, minutes) = convert_runtime(runtime);
let size = convert_to_gb(size_on_disk.as_u64().unwrap()); let size = convert_to_gb(size_on_disk);
let quality_profile = app let quality_profile = app
.data .data
.radarr_data .radarr_data
.quality_profile_map .quality_profile_map
.get_by_left(&quality_profile_id.as_u64().unwrap()) .get_by_left(&quality_profile_id)
.unwrap() .unwrap()
.to_owned(); .to_owned();
let imdb_rating = if let Some(rating) = ratings.imdb { let imdb_rating = if let Some(rating) = ratings.imdb {
if let Some(value) = rating.value.as_f64() { if let Some(value) = rating.value.as_f64() {
format!("{:.1}", value) format!("{:.1}", value)
} else { } else {
"".to_owned() String::new()
} }
} else { } else {
"".to_owned() String::new()
}; };
let tmdb_rating = if let Some(rating) = ratings.tmdb { let tmdb_rating = if let Some(rating) = ratings.tmdb {
if let Some(value) = rating.value.as_f64() { if let Some(value) = rating.value.as_f64() {
format!("{}%", (value * 10f64).ceil()) format!("{}%", (value * 10f64).ceil())
} else { } else {
"".to_owned() String::new()
} }
} else { } else {
"".to_owned() String::new()
}; };
let rotten_tomatoes_rating = if let Some(rating) = ratings.rotten_tomatoes { let rotten_tomatoes_rating = if let Some(rating) = ratings.rotten_tomatoes {
if let Some(value) = rating.value.as_u64() { if let Some(value) = rating.value.as_u64() {
format!("{}%", value) format!("{}%", value)
} else { } else {
"".to_owned() String::new()
} }
} else { } else {
"".to_owned() String::new()
}; };
let status = get_movie_status(has_file, &app.data.radarr_data.downloads.items, id); let status = get_movie_status(has_file, &app.data.radarr_data.downloads.items, id);
@@ -928,36 +892,23 @@ impl<'a, 'b> Network<'a, 'b> {
let mut movie_details_modal = MovieDetailsModal { let mut movie_details_modal = MovieDetailsModal {
movie_details: ScrollableText::with_string(formatdoc!( movie_details: ScrollableText::with_string(formatdoc!(
"Title: {} "Title: {title}
Year: {} Year: {year}
Runtime: {}h {}m Runtime: {hours}h {minutes}m
Rating: {} Rating: {}
Collection: {} Collection: {}
Status: {} Status: {status}
Description: {} Description: {overview}
TMDB: {} TMDB: {tmdb_rating}
IMDB: {} IMDB: {imdb_rating}
Rotten Tomatoes: {} Rotten Tomatoes: {rotten_tomatoes_rating}
Quality Profile: {} Quality Profile: {quality_profile}
Size: {:.2} GB Size: {size:.2} GB
Path: {} Path: {path}
Studio: {} Studio: {studio}
Genres: {}", Genres: {}",
title,
year,
hours,
minutes,
certification.unwrap_or_default(), certification.unwrap_or_default(),
collection.title, collection.title,
status,
overview,
tmdb_rating,
imdb_rating,
rotten_tomatoes_rating,
quality_profile,
size,
path,
studio,
genres.join(", ") genres.join(", ")
)), )),
..MovieDetailsModal::default() ..MovieDetailsModal::default()
@@ -967,11 +918,10 @@ impl<'a, 'b> Network<'a, 'b> {
movie_details_modal.file_details = formatdoc!( movie_details_modal.file_details = formatdoc!(
"Relative Path: {} "Relative Path: {}
Absolute Path: {} Absolute Path: {}
Size: {:.2} GB Size: {size:.2} GB
Date Added: {}", Date Added: {}",
file.relative_path, file.relative_path,
file.path, file.path,
size,
file.date_added file.date_added
); );
@@ -982,11 +932,11 @@ impl<'a, 'b> Network<'a, 'b> {
Codec: {} Codec: {}
Languages: {} Languages: {}
Stream Count: {}", Stream Count: {}",
media_info.audio_bitrate.as_u64().unwrap(), media_info.audio_bitrate,
media_info.audio_channels.as_f64().unwrap(), media_info.audio_channels.as_f64().unwrap(),
media_info.audio_codec.unwrap_or_default(), media_info.audio_codec.unwrap_or_default(),
media_info.audio_languages.unwrap_or_default(), media_info.audio_languages.unwrap_or_default(),
media_info.audio_stream_count.as_u64().unwrap() media_info.audio_stream_count
); );
movie_details_modal.video_details = formatdoc!( movie_details_modal.video_details = formatdoc!(
@@ -997,8 +947,8 @@ impl<'a, 'b> Network<'a, 'b> {
Resolution: {} Resolution: {}
Scan Type: {} Scan Type: {}
Runtime: {}", Runtime: {}",
media_info.video_bit_depth.as_u64().unwrap(), media_info.video_bit_depth,
media_info.video_bitrate.as_u64().unwrap(), media_info.video_bitrate,
media_info.video_codec, media_info.video_codec,
media_info.video_fps.as_f64().unwrap(), media_info.video_fps.as_f64().unwrap(),
media_info.resolution, media_info.resolution,
@@ -1073,7 +1023,7 @@ impl<'a, 'b> Network<'a, 'b> {
.handle_request::<(), Vec<QualityProfile>>(request_props, |quality_profiles, mut app| { .handle_request::<(), Vec<QualityProfile>>(request_props, |quality_profiles, mut app| {
app.data.radarr_data.quality_profile_map = quality_profiles app.data.radarr_data.quality_profile_map = quality_profiles
.into_iter() .into_iter()
.map(|profile| (profile.id.as_u64().unwrap(), profile.name)) .map(|profile| (profile.id, profile.name))
.collect(); .collect();
}) })
.await; .await;
@@ -1103,19 +1053,11 @@ impl<'a, 'b> Network<'a, 'b> {
async fn get_releases(&mut self) { async fn get_releases(&mut self) {
let (movie_id, tmdb_id) = self.extract_movie_id().await; let (movie_id, tmdb_id) = self.extract_movie_id().await;
info!( info!("Fetching releases for movie with TMDB id {tmdb_id} and with Radarr id: {movie_id}");
"Fetching releases for movie with TMDB id {} and with Radarr id: {}",
tmdb_id, movie_id
);
let request_props = self let request_props = self
.radarr_request_props_from( .radarr_request_props_from(
format!( format!("{}?movieId={movie_id}", RadarrEvent::GetReleases.resource()).as_str(),
"{}?movieId={}",
RadarrEvent::GetReleases.resource(),
movie_id
)
.as_str(),
RequestMethod::Get, RequestMethod::Get,
None::<()>, None::<()>,
) )
@@ -1181,7 +1123,7 @@ impl<'a, 'b> Network<'a, 'b> {
.handle_request::<(), Vec<Tag>>(request_props, |tags_vec, mut app| { .handle_request::<(), Vec<Tag>>(request_props, |tags_vec, mut app| {
app.data.radarr_data.tags_map = tags_vec app.data.radarr_data.tags_map = tags_vec
.into_iter() .into_iter()
.map(|tag| (tag.id.as_u64().unwrap(), tag.label)) .map(|tag| (tag.id, tag.label))
.collect(); .collect();
}) })
.await; .await;
@@ -1241,56 +1183,46 @@ impl<'a, 'b> Network<'a, 'b> {
let vec_to_bullet_points = |vec: Vec<String>| { let vec_to_bullet_points = |vec: Vec<String>| {
vec vec
.iter() .iter()
.map(|change| format!(" * {}", change)) .map(|change| format!(" * {change}"))
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n") .join("\n")
}; };
let mut update_info = formatdoc!( let mut update_info = formatdoc!(
"{} - {} {} "{} - {} {install_status}
{}", {}",
update.version, update.version,
update.release_date, update.release_date,
install_status,
"-".repeat(200) "-".repeat(200)
); );
if let Some(new_changes) = update.changes.new { if let Some(new_changes) = update.changes.new {
let changes = vec_to_bullet_points(new_changes); let changes = vec_to_bullet_points(new_changes);
update_info = formatdoc!( update_info = formatdoc!(
"{} "{update_info}
New: New:
{}", {changes}"
update_info,
changes
) )
} }
if let Some(fixes) = update.changes.fixed { if let Some(fixes) = update.changes.fixed {
let fixes = vec_to_bullet_points(fixes); let fixes = vec_to_bullet_points(fixes);
update_info = formatdoc!( update_info = formatdoc!(
"{} "{update_info}
Fixed: Fixed:
{}", {fixes}"
update_info,
fixes
); );
} }
update_info update_info
}) })
.reduce(|version_1, version_2| format!("{}\n\n\n{}", version_1, version_2)) .reduce(|version_1, version_2| format!("{version_1}\n\n\n{version_2}"))
.unwrap(); .unwrap();
app.data.radarr_data.updates = ScrollableText::with_string(formatdoc!( app.data.radarr_data.updates = ScrollableText::with_string(formatdoc!(
"{} "The latest version of Radarr is {latest_installed} installed
{}", {updates}"
format!(
"The latest version of Radarr is {} installed",
latest_installed
),
updates
)); ));
}) })
.await; .await;
@@ -1343,10 +1275,9 @@ impl<'a, 'b> Network<'a, 'b> {
} }
Err(e) => { Err(e) => {
warn!( warn!(
"Encountered a race condition: {}\n \ "Encountered a race condition: {e}\n \
This is most likely caused by the user trying to navigate between modals rapidly. \ This is most likely caused by the user trying to navigate between modals rapidly. \
Ignoring search request.", Ignoring search request."
e
); );
} }
} }
@@ -1364,7 +1295,7 @@ impl<'a, 'b> Network<'a, 'b> {
.task_name .task_name
.clone(); .clone();
info!("Starting Radarr task: {}", task_name); info!("Starting Radarr task: {task_name}");
let body = CommandBody { name: task_name }; let body = CommandBody { name: task_name };
@@ -1383,10 +1314,7 @@ impl<'a, 'b> Network<'a, 'b> {
async fn trigger_automatic_search(&mut self) { async fn trigger_automatic_search(&mut self) {
let (movie_id, tmdb_id) = self.extract_movie_id().await; let (movie_id, tmdb_id) = self.extract_movie_id().await;
info!( info!("Searching indexers for movie with TMDB id {tmdb_id} and with Radarr id: {movie_id}");
"Searching indexers for movie with TMDB id {} and with Radarr id: {}",
tmdb_id, movie_id
);
let body = MovieCommandBody { let body = MovieCommandBody {
name: "MoviesSearch".to_owned(), name: "MoviesSearch".to_owned(),
movie_ids: vec![movie_id], movie_ids: vec![movie_id],
@@ -1427,10 +1355,7 @@ impl<'a, 'b> Network<'a, 'b> {
async fn update_and_scan(&mut self) { async fn update_and_scan(&mut self) {
let (movie_id, tmdb_id) = self.extract_movie_id().await; let (movie_id, tmdb_id) = self.extract_movie_id().await;
info!( info!("Updating and scanning movie with TMDB id {tmdb_id} and with Radarr id: {movie_id}");
"Updating and scanning movie with TMDB id {} and with Radarr id: {}",
tmdb_id, movie_id
);
let body = MovieCommandBody { let body = MovieCommandBody {
name: "RefreshMovie".to_owned(), name: "RefreshMovie".to_owned(),
movie_ids: vec![movie_id], movie_ids: vec![movie_id],
@@ -1501,7 +1426,7 @@ impl<'a, 'b> Network<'a, 'b> {
.unwrap() .unwrap()
.clone(); .clone();
debug!("Indexer settings body: {:?}", body); debug!("Indexer settings body: {body:?}");
let request_props = self let request_props = self
.radarr_request_props_from( .radarr_request_props_from(
@@ -1530,12 +1455,7 @@ impl<'a, 'b> Network<'a, 'b> {
port, port,
api_token, api_token,
} = &app.config.radarr; } = &app.config.radarr;
let uri = format!( let uri = format!("http://{host}:{}/api/v3{resource}", port.unwrap_or(7878));
"http://{}:{}/api/v3{}",
host,
port.unwrap_or(7878),
resource
);
RequestProps { RequestProps {
uri, uri,
@@ -1545,7 +1465,7 @@ impl<'a, 'b> Network<'a, 'b> {
} }
} }
async fn extract_and_add_tag_ids_vec(&mut self, edit_tags: String) -> Vec<u64> { async fn extract_and_add_tag_ids_vec(&mut self, edit_tags: String) -> Vec<i64> {
let tags_map = self.app.lock().await.data.radarr_data.tags_map.clone(); let tags_map = self.app.lock().await.data.radarr_data.tags_map.clone();
let tags = edit_tags.clone(); let tags = edit_tags.clone();
let missing_tags_vec = edit_tags let missing_tags_vec = edit_tags
@@ -1572,7 +1492,7 @@ impl<'a, 'b> Network<'a, 'b> {
.collect() .collect()
} }
async fn extract_movie_id(&mut self) -> (u64, u64) { async fn extract_movie_id(&mut self) -> (i64, i64) {
let app = self.app.lock().await; let app = self.app.lock().await;
if app.data.radarr_data.filtered_movies.is_some() { if app.data.radarr_data.filtered_movies.is_some() {
( (
@@ -1583,9 +1503,7 @@ impl<'a, 'b> Network<'a, 'b> {
.as_ref() .as_ref()
.unwrap() .unwrap()
.current_selection() .current_selection()
.id .id,
.as_u64()
.unwrap(),
app app
.data .data
.radarr_data .radarr_data
@@ -1593,33 +1511,17 @@ impl<'a, 'b> Network<'a, 'b> {
.as_ref() .as_ref()
.unwrap() .unwrap()
.current_selection() .current_selection()
.tmdb_id .tmdb_id,
.as_u64()
.unwrap(),
) )
} else { } else {
( (
app app.data.radarr_data.movies.current_selection().id,
.data app.data.radarr_data.movies.current_selection().tmdb_id,
.radarr_data
.movies
.current_selection()
.id
.as_u64()
.unwrap(),
app
.data
.radarr_data
.movies
.current_selection()
.tmdb_id
.as_u64()
.unwrap(),
) )
} }
} }
async fn extract_collection_id(&mut self) -> u64 { async fn extract_collection_id(&mut self) -> i64 {
if self if self
.app .app
.lock() .lock()
@@ -1640,8 +1542,6 @@ impl<'a, 'b> Network<'a, 'b> {
.unwrap() .unwrap()
.current_selection() .current_selection()
.id .id
.as_u64()
.unwrap()
} else { } else {
self self
.app .app
@@ -1652,22 +1552,20 @@ impl<'a, 'b> Network<'a, 'b> {
.collections .collections
.current_selection() .current_selection()
.id .id
.as_u64()
.unwrap()
} }
} }
async fn append_movie_id_param(&mut self, resource: &str) -> String { async fn append_movie_id_param(&mut self, resource: &str) -> String {
let (movie_id, _) = self.extract_movie_id().await; let (movie_id, _) = self.extract_movie_id().await;
format!("{}?movieId={}", resource, movie_id) format!("{resource}?movieId={movie_id}")
} }
} }
fn get_movie_status(has_file: bool, downloads_vec: &[DownloadRecord], movie_id: Number) -> String { fn get_movie_status(has_file: bool, downloads_vec: &[DownloadRecord], movie_id: i64) -> String {
if !has_file { if !has_file {
if let Some(download) = downloads_vec if let Some(download) = downloads_vec
.iter() .iter()
.find(|&download| download.movie_id.as_u64().unwrap() == movie_id.as_u64().unwrap()) .find(|&download| download.movie_id == movie_id)
{ {
if download.status == "downloading" { if download.status == "downloading" {
return "Downloading".to_owned(); return "Downloading".to_owned();
+78 -86
View File
@@ -7,7 +7,7 @@ mod test {
use mockito::{Matcher, Mock, Server, ServerGuard}; use mockito::{Matcher, Mock, Server, ServerGuard};
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest; use rstest::rstest;
use serde_json::{json, Value}; use serde_json::{json, Number, Value};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
@@ -229,12 +229,12 @@ mod test {
app_arc.lock().await.data.radarr_data.disk_space_vec, app_arc.lock().await.data.radarr_data.disk_space_vec,
vec![ vec![
DiskSpace { DiskSpace {
free_space: Number::from(1111), free_space: 1111,
total_space: Number::from(2222), total_space: 2222,
}, },
DiskSpace { DiskSpace {
free_space: Number::from(3333), free_space: 3333,
total_space: Number::from(4444), total_space: 4444,
}, },
] ]
); );
@@ -270,7 +270,7 @@ mod test {
let (async_server, app_arc, _server) = mock_radarr_api( let (async_server, app_arc, _server) = mock_radarr_api(
RequestMethod::Get, RequestMethod::Get,
None, None,
Some(serde_json::from_str(format!("[ {} ]", MOVIE_JSON).as_str()).unwrap()), Some(serde_json::from_str(format!("[ {MOVIE_JSON} ]").as_str()).unwrap()),
RadarrEvent::GetMovies.resource(), RadarrEvent::GetMovies.resource(),
) )
.await; .await;
@@ -463,7 +463,7 @@ mod test {
let mut async_server = server let mut async_server = server
.mock( .mock(
&RequestMethod::Get.to_string().to_uppercase(), &RequestMethod::Get.to_string().to_uppercase(),
format!("/api/v3{}", resource).as_str(), format!("/api/v3{resource}").as_str(),
) )
.match_header("X-Api-Key", "test1234"); .match_header("X-Api-Key", "test1234");
async_server = async_server.expect_at_most(0).create_async().await; async_server = async_server.expect_at_most(0).create_async().await;
@@ -1186,7 +1186,7 @@ mod test {
async_server.assert_async().await; async_server.assert_async().await;
assert_eq!( assert_eq!(
app_arc.lock().await.data.radarr_data.quality_profile_map, app_arc.lock().await.data.radarr_data.quality_profile_map,
BiMap::from_iter([(2222u64, "HD - 1080p".to_owned())]) BiMap::from_iter([(2222i64, "HD - 1080p".to_owned())])
); );
} }
@@ -1210,7 +1210,7 @@ mod test {
async_server.assert_async().await; async_server.assert_async().await;
assert_eq!( assert_eq!(
app_arc.lock().await.data.radarr_data.tags_map, app_arc.lock().await.data.radarr_data.tags_map,
BiMap::from_iter([(2222u64, "usenet".to_owned())]) BiMap::from_iter([(2222i64, "usenet".to_owned())])
); );
} }
@@ -1237,7 +1237,7 @@ mod test {
Task { Task {
name: "Application Check Update".to_owned(), name: "Application Check Update".to_owned(),
task_name: "ApplicationCheckUpdate".to_owned(), task_name: "ApplicationCheckUpdate".to_owned(),
interval: Number::from(360), interval: 360,
last_execution: timestamp, last_execution: timestamp,
next_execution: timestamp, next_execution: timestamp,
last_duration: "00:00:00.5111547".to_owned(), last_duration: "00:00:00.5111547".to_owned(),
@@ -1245,7 +1245,7 @@ mod test {
Task { Task {
name: "Backup".to_owned(), name: "Backup".to_owned(),
task_name: "Backup".to_owned(), task_name: "Backup".to_owned(),
interval: Number::from(10080), interval: 10080,
last_execution: timestamp, last_execution: timestamp,
next_execution: timestamp, next_execution: timestamp,
last_duration: "00:00:00.5111547".to_owned(), last_duration: "00:00:00.5111547".to_owned(),
@@ -1317,7 +1317,7 @@ mod test {
The latest version of Radarr is already installed The latest version of Radarr is already installed
4.3.2.1 - 2023-04-15 02:02:53 UTC (Currently Installed) 4.3.2.1 - 2023-04-15 02:02:53 UTC (Currently Installed)
{} {line_break}
New: New:
* Cool new thing * Cool new thing
Fixed: Fixed:
@@ -1325,20 +1325,17 @@ mod test {
3.2.1.0 - 2023-04-15 02:02:53 UTC (Previously Installed) 3.2.1.0 - 2023-04-15 02:02:53 UTC (Previously Installed)
{} {line_break}
New: New:
* Cool new thing (old) * Cool new thing (old)
* Other cool new thing (old) * Other cool new thing (old)
2.1.0 - 2023-04-15 02:02:53 UTC 2.1.0 - 2023-04-15 02:02:53 UTC
{} {line_break}
Fixed: Fixed:
* Killed bug 1 * Killed bug 1
* Fixed bug 2", * Fixed bug 2"
line_break.clone(),
line_break.clone(),
line_break
)); ));
let (async_server, app_arc, _server) = mock_radarr_api( let (async_server, app_arc, _server) = mock_radarr_api(
RequestMethod::Get, RequestMethod::Get,
@@ -1569,17 +1566,17 @@ mod test {
}; };
add_movie_modal.root_folder_list.set_items(vec![ add_movie_modal.root_folder_list.set_items(vec![
RootFolder { RootFolder {
id: Number::from(1), id: 1,
path: "/nfs".to_owned(), path: "/nfs".to_owned(),
accessible: true, accessible: true,
free_space: Number::from(219902325555200u64), free_space: 219902325555200,
unmapped_folders: None, unmapped_folders: None,
}, },
RootFolder { RootFolder {
id: Number::from(2), id: 2,
path: "/nfs2".to_owned(), path: "/nfs2".to_owned(),
accessible: true, accessible: true,
free_space: Number::from(21990232555520u64), free_space: 21990232555520,
unmapped_folders: None, unmapped_folders: None,
}, },
]); ]);
@@ -1655,17 +1652,17 @@ mod test {
}; };
add_movie_modal.root_folder_list.set_items(vec![ add_movie_modal.root_folder_list.set_items(vec![
RootFolder { RootFolder {
id: Number::from(1), id: 1,
path: "/nfs".to_owned(), path: "/nfs".to_owned(),
accessible: true, accessible: true,
free_space: Number::from(219902325555200u64), free_space: 219902325555200,
unmapped_folders: None, unmapped_folders: None,
}, },
RootFolder { RootFolder {
id: Number::from(2), id: 2,
path: "/nfs2".to_owned(), path: "/nfs2".to_owned(),
accessible: true, accessible: true,
free_space: Number::from(21990232555520u64), free_space: 21990232555520,
unmapped_folders: None, unmapped_folders: None,
}, },
]); ]);
@@ -1685,7 +1682,7 @@ mod test {
app.data.radarr_data.tags_map = app.data.radarr_data.tags_map =
BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]);
let secondary_search_result = AddMovieSearchResult { let secondary_search_result = AddMovieSearchResult {
tmdb_id: Number::from(5678), tmdb_id: 5678,
..add_movie_search_result() ..add_movie_search_result()
}; };
let mut add_searched_movies = StatefulTable::default(); let mut add_searched_movies = StatefulTable::default();
@@ -1715,9 +1712,7 @@ mod test {
.as_ref() .as_ref()
.unwrap() .unwrap()
.current_selection() .current_selection()
.tmdb_id .tmdb_id,
.as_u64()
.unwrap(),
5678 5678
); );
} }
@@ -2006,8 +2001,8 @@ mod test {
.radarr_data .radarr_data
.movies .movies
.set_items(vec![Movie { .set_items(vec![Movie {
id: Number::from(1), id: 1,
tmdb_id: Number::from(2), tmdb_id: 2,
..Movie::default() ..Movie::default()
}]); }]);
let mut network = Network::new(&app_arc, CancellationToken::new()); let mut network = Network::new(&app_arc, CancellationToken::new());
@@ -2020,8 +2015,8 @@ mod test {
let app_arc = Arc::new(Mutex::new(App::default())); let app_arc = Arc::new(Mutex::new(App::default()));
let mut filtered_movies = StatefulTable::default(); let mut filtered_movies = StatefulTable::default();
filtered_movies.set_items(vec![Movie { filtered_movies.set_items(vec![Movie {
id: Number::from(1), id: 1,
tmdb_id: Number::from(2), tmdb_id: 2,
..Movie::default() ..Movie::default()
}]); }]);
app_arc.lock().await.data.radarr_data.filtered_movies = Some(filtered_movies); app_arc.lock().await.data.radarr_data.filtered_movies = Some(filtered_movies);
@@ -2040,7 +2035,7 @@ mod test {
.radarr_data .radarr_data
.collections .collections
.set_items(vec![Collection { .set_items(vec![Collection {
id: Number::from(1), id: 1,
..Collection::default() ..Collection::default()
}]); }]);
let mut network = Network::new(&app_arc, CancellationToken::new()); let mut network = Network::new(&app_arc, CancellationToken::new());
@@ -2053,7 +2048,7 @@ mod test {
let app_arc = Arc::new(Mutex::new(App::default())); let app_arc = Arc::new(Mutex::new(App::default()));
let mut filtered_collections = StatefulTable::default(); let mut filtered_collections = StatefulTable::default();
filtered_collections.set_items(vec![Collection { filtered_collections.set_items(vec![Collection {
id: Number::from(1), id: 1,
..Collection::default() ..Collection::default()
}]); }]);
app_arc.lock().await.data.radarr_data.filtered_collections = Some(filtered_collections); app_arc.lock().await.data.radarr_data.filtered_collections = Some(filtered_collections);
@@ -2072,7 +2067,7 @@ mod test {
.radarr_data .radarr_data
.movies .movies
.set_items(vec![Movie { .set_items(vec![Movie {
id: Number::from(1), id: 1,
..Movie::default() ..Movie::default()
}]); }]);
let mut network = Network::new(&app_arc, CancellationToken::new()); let mut network = Network::new(&app_arc, CancellationToken::new());
@@ -2127,25 +2122,22 @@ mod test {
#[test] #[test]
fn test_get_movie_status_downloaded() { fn test_get_movie_status_downloaded() {
assert_str_eq!(get_movie_status(true, &[], Number::from(0)), "Downloaded"); assert_str_eq!(get_movie_status(true, &[], 0), "Downloaded");
} }
#[test] #[test]
fn test_get_movie_status_missing() { fn test_get_movie_status_missing() {
let download_record = DownloadRecord { let download_record = DownloadRecord {
movie_id: 1.into(), movie_id: 1,
..DownloadRecord::default() ..DownloadRecord::default()
}; };
assert_str_eq!( assert_str_eq!(
get_movie_status(false, &[download_record.clone()], 0.into()), get_movie_status(false, &[download_record.clone()], 0),
"Missing" "Missing"
); );
assert_str_eq!( assert_str_eq!(get_movie_status(false, &[download_record], 1), "Missing");
get_movie_status(false, &[download_record], 1.into()),
"Missing"
);
} }
#[test] #[test]
@@ -2154,11 +2146,11 @@ mod test {
get_movie_status( get_movie_status(
false, false,
&[DownloadRecord { &[DownloadRecord {
movie_id: 1.into(), movie_id: 1,
status: "downloading".to_owned(), status: "downloading".to_owned(),
..DownloadRecord::default() ..DownloadRecord::default()
}], }],
1.into() 1
), ),
"Downloading" "Downloading"
); );
@@ -2170,11 +2162,11 @@ mod test {
get_movie_status( get_movie_status(
false, false,
&[DownloadRecord { &[DownloadRecord {
movie_id: 1.into(), movie_id: 1,
status: "completed".to_owned(), status: "completed".to_owned(),
..DownloadRecord::default() ..DownloadRecord::default()
}], }],
1.into() 1
), ),
"Awaiting Import" "Awaiting Import"
); );
@@ -2190,7 +2182,7 @@ mod test {
let mut async_server = server let mut async_server = server
.mock( .mock(
&method.to_string().to_uppercase(), &method.to_string().to_uppercase(),
format!("/api/v3{}", resource).as_str(), format!("/api/v3{resource}").as_str(),
) )
.match_header("X-Api-Key", "test1234"); .match_header("X-Api-Key", "test1234");
@@ -2248,13 +2240,13 @@ mod test {
fn media_info() -> MediaInfo { fn media_info() -> MediaInfo {
MediaInfo { MediaInfo {
audio_bitrate: Number::from(0), audio_bitrate: 0,
audio_channels: Number::from_f64(7.1).unwrap(), audio_channels: Number::from_f64(7.1).unwrap(),
audio_codec: Some("AAC".to_owned()), audio_codec: Some("AAC".to_owned()),
audio_languages: Some("eng".to_owned()), audio_languages: Some("eng".to_owned()),
audio_stream_count: Number::from(1), audio_stream_count: 1,
video_bit_depth: Number::from(10), video_bit_depth: 10,
video_bitrate: Number::from(0), video_bitrate: 0,
video_codec: "x265".to_owned(), video_codec: "x265".to_owned(),
video_fps: Number::from_f64(23.976).unwrap(), video_fps: Number::from_f64(23.976).unwrap(),
resolution: "1920x804".to_owned(), resolution: "1920x804".to_owned(),
@@ -2276,9 +2268,9 @@ mod test {
CollectionMovie { CollectionMovie {
title: "Test".to_owned().into(), title: "Test".to_owned().into(),
overview: "Collection blah blah blah".to_owned(), overview: "Collection blah blah blah".to_owned(),
year: Number::from(2023), year: 2023,
runtime: Number::from(120), runtime: 120,
tmdb_id: Number::from(1234), tmdb_id: 1234,
genres: genres(), genres: genres(),
ratings: ratings_list(), ratings: ratings_list(),
} }
@@ -2286,35 +2278,35 @@ mod test {
fn collection() -> Collection { fn collection() -> Collection {
Collection { Collection {
id: Number::from(123), id: 123,
title: "Test Collection".to_owned().into(), title: "Test Collection".to_owned().into(),
root_folder_path: Some("/nfs/movies".to_owned()), root_folder_path: Some("/nfs/movies".to_owned()),
search_on_add: true, search_on_add: true,
monitored: true, monitored: true,
minimum_availability: MinimumAvailability::Released, minimum_availability: MinimumAvailability::Released,
overview: Some("Collection blah blah blah".to_owned()), overview: Some("Collection blah blah blah".to_owned()),
quality_profile_id: Number::from(2222), quality_profile_id: 2222,
movies: Some(vec![collection_movie()]), movies: Some(vec![collection_movie()]),
} }
} }
fn movie() -> Movie { fn movie() -> Movie {
Movie { Movie {
id: Number::from(1), id: 1,
title: "Test".to_owned().into(), title: "Test".to_owned().into(),
original_language: language(), original_language: language(),
size_on_disk: Number::from(3543348019u64), size_on_disk: 3543348019,
status: "Downloaded".to_owned(), status: "Downloaded".to_owned(),
overview: "Blah blah blah".to_owned(), overview: "Blah blah blah".to_owned(),
path: "/nfs/movies".to_owned(), path: "/nfs/movies".to_owned(),
studio: "21st Century Alex".to_owned(), studio: "21st Century Alex".to_owned(),
genres: genres(), genres: genres(),
year: Number::from(2023), year: 2023,
monitored: true, monitored: true,
has_file: true, has_file: true,
runtime: Number::from(120), runtime: 120,
tmdb_id: Number::from(1234), tmdb_id: 1234,
quality_profile_id: Number::from(2222), quality_profile_id: 2222,
minimum_availability: MinimumAvailability::Announced, minimum_availability: MinimumAvailability::Announced,
certification: Some("R".to_owned()), certification: Some("R".to_owned()),
tags: vec![Number::from(1)], tags: vec![Number::from(1)],
@@ -2345,11 +2337,11 @@ mod test {
Release { Release {
guid: "1234".to_owned(), guid: "1234".to_owned(),
protocol: "torrent".to_owned(), protocol: "torrent".to_owned(),
age: Number::from(1), age: 1,
title: HorizontallyScrollableText::from("Test Release"), title: HorizontallyScrollableText::from("Test Release"),
indexer: "kickass torrents".to_owned(), indexer: "kickass torrents".to_owned(),
indexer_id: Number::from(2), indexer_id: 2,
size: Number::from(1234), size: 1234,
rejected: true, rejected: true,
rejections: Some(rejections()), rejections: Some(rejections()),
seeders: Some(Number::from(2)), seeders: Some(Number::from(2)),
@@ -2361,14 +2353,14 @@ mod test {
fn add_movie_search_result() -> AddMovieSearchResult { fn add_movie_search_result() -> AddMovieSearchResult {
AddMovieSearchResult { AddMovieSearchResult {
tmdb_id: Number::from(1234), tmdb_id: 1234,
title: HorizontallyScrollableText::from("Test"), title: HorizontallyScrollableText::from("Test"),
original_language: language(), original_language: language(),
status: "released".to_owned(), status: "released".to_owned(),
overview: "New movie blah blah blah".to_owned(), overview: "New movie blah blah blah".to_owned(),
genres: genres(), genres: genres(),
year: Number::from(2023), year: 2023,
runtime: Number::from(120), runtime: 120,
ratings: ratings_list(), ratings: ratings_list(),
} }
} }
@@ -2387,10 +2379,10 @@ mod test {
DownloadRecord { DownloadRecord {
title: "Test Download Title".to_owned(), title: "Test Download Title".to_owned(),
status: "downloading".to_owned(), status: "downloading".to_owned(),
id: Number::from(1), id: 1,
movie_id: Number::from(1), movie_id: 1,
size: Number::from(3543348019u64), size: 3543348019,
sizeleft: Number::from(1771674009u64), sizeleft: 1771674009,
output_path: Some(HorizontallyScrollableText::from("/nfs/movies/Test")), output_path: Some(HorizontallyScrollableText::from("/nfs/movies/Test")),
indexer: "kickass torrents".to_owned(), indexer: "kickass torrents".to_owned(),
download_client: "transmission".to_owned(), download_client: "transmission".to_owned(),
@@ -2405,10 +2397,10 @@ mod test {
fn root_folder() -> RootFolder { fn root_folder() -> RootFolder {
RootFolder { RootFolder {
id: Number::from(1), id: 1,
path: "/nfs".to_owned(), path: "/nfs".to_owned(),
accessible: true, accessible: true,
free_space: Number::from(219902325555200u64), free_space: 219902325555200,
unmapped_folders: None, unmapped_folders: None,
} }
} }
@@ -2441,17 +2433,17 @@ mod test {
supports_rss: true, supports_rss: true,
supports_search: true, supports_search: true,
protocol: "torrent".to_owned(), protocol: "torrent".to_owned(),
priority: Number::from(25), priority: 25,
download_client_id: Number::from(0), download_client_id: 0,
name: Some("Test Indexer".to_owned()), name: Some("Test Indexer".to_owned()),
implementation_name: Some("Torznab".to_owned()), implementation_name: Some("Torznab".to_owned()),
implementation: Some("Torznab".to_owned()), implementation: Some("Torznab".to_owned()),
config_contract: Some("TorznabSettings".to_owned()), config_contract: Some("TorznabSettings".to_owned()),
tags: Some(vec!["test_tag".to_owned()]), tags: Some(vec!["test_tag".to_owned()]),
id: Number::from(1), id: 1,
fields: Some(vec![ fields: Some(vec![
IndexerField { IndexerField {
order: Number::from(0), order: 0,
name: Some("valueIsString".to_owned()), name: Some("valueIsString".to_owned()),
label: Some("Value Is String".to_owned()), label: Some("Value Is String".to_owned()),
value: Some(json!("hello")), value: Some(json!("hello")),
@@ -2459,19 +2451,19 @@ mod test {
select_options: None, select_options: None,
}, },
IndexerField { IndexerField {
order: Number::from(1), order: 1,
name: Some("emptyValueWithSelectOptions".to_owned()), name: Some("emptyValueWithSelectOptions".to_owned()),
label: Some("Empty Value With Select Options".to_owned()), label: Some("Empty Value With Select Options".to_owned()),
value: None, value: None,
field_type: Some("select".to_owned()), field_type: Some("select".to_owned()),
select_options: Some(vec![IndexerSelectOption { select_options: Some(vec![IndexerSelectOption {
value: Number::from(-2), value: -2,
name: Some("Original".to_owned()), name: Some("Original".to_owned()),
order: Number::from(0), order: 0,
}]), }]),
}, },
IndexerField { IndexerField {
order: Number::from(2), order: 2,
name: Some("valueIsAnArray".to_owned()), name: Some("valueIsAnArray".to_owned()),
label: Some("Value is an array".to_owned()), label: Some("Value is an array".to_owned()),
value: Some(json!([1, 2])), value: Some(json!([1, 2])),
@@ -2484,9 +2476,9 @@ mod test {
fn indexer_settings() -> IndexerSettings { fn indexer_settings() -> IndexerSettings {
IndexerSettings { IndexerSettings {
rss_sync_interval: Number::from(60), rss_sync_interval: 60,
allow_hardcoded_subs: true, allow_hardcoded_subs: true,
id: Number::from(1), id: 1,
..IndexerSettings::default() ..IndexerSettings::default()
} }
} }
+4 -4
View File
@@ -545,7 +545,7 @@ pub fn draw_checkbox_with_label<B: Backend>(
area, area,
); );
let label_paragraph = Paragraph::new(Text::from(format!("\n{}: ", label))) let label_paragraph = Paragraph::new(Text::from(format!("\n{label}: ")))
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Right) .alignment(Alignment::Right)
.style(style_primary()); .style(style_primary());
@@ -605,7 +605,7 @@ pub fn draw_drop_down_menu_button<B: Backend>(
area, area,
); );
let description_paragraph = Paragraph::new(Text::from(format!("\n{}: ", description))) let description_paragraph = Paragraph::new(Text::from(format!("\n{description}: ")))
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Right) .alignment(Alignment::Right)
.style(style_primary()); .style(style_primary());
@@ -676,7 +676,7 @@ fn draw_help_and_get_content_rect<B: Backend>(
let chunks = let chunks =
vertical_chunks_with_margin(vec![Constraint::Min(0), Constraint::Length(2)], area, 1); vertical_chunks_with_margin(vec![Constraint::Min(0), Constraint::Length(2)], area, 1);
let mut help_test = Text::from(format!(" {}", help_string)); let mut help_test = Text::from(format!(" {help_string}"));
help_test.patch_style(style_help()); help_test.patch_style(style_help());
let help_paragraph = Paragraph::new(help_test) let help_paragraph = Paragraph::new(help_test)
.block(layout_block_top_border()) .block(layout_block_top_border())
@@ -739,7 +739,7 @@ pub fn draw_text_box_with_label<B: Backend>(
area, area,
); );
let label_paragraph = Paragraph::new(Text::from(format!("\n{}: ", label))) let label_paragraph = Paragraph::new(Text::from(format!("\n{label}: ")))
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Right) .alignment(Alignment::Right)
.style(style_primary()); .style(style_primary());
@@ -25,7 +25,7 @@ use crate::utils::convert_runtime;
#[path = "collection_details_ui_tests.rs"] #[path = "collection_details_ui_tests.rs"]
mod collection_details_ui_tests; mod collection_details_ui_tests;
pub(super) struct CollectionDetailsUi {} pub(super) struct CollectionDetailsUi;
impl DrawUi for CollectionDetailsUi { impl DrawUi for CollectionDetailsUi {
fn accepts(route: Route) -> bool { fn accepts(route: Route) -> bool {
@@ -90,7 +90,7 @@ pub fn draw_collection_details<B: Backend>(
.data .data
.radarr_data .radarr_data
.quality_profile_map .quality_profile_map
.get_by_left(&collection_selection.quality_profile_id.as_u64().unwrap()) .get_by_left(&collection_selection.quality_profile_id)
.unwrap() .unwrap()
.to_owned(); .to_owned();
let current_selection = if app.data.radarr_data.collection_movies.items.is_empty() { let current_selection = if app.data.radarr_data.collection_movies.items.is_empty() {
@@ -198,7 +198,7 @@ pub fn draw_collection_details<B: Backend>(
current_selection == *movie, current_selection == *movie,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count % app.ticks_until_scroll == 0,
); );
let (hours, minutes) = convert_runtime(movie.runtime.as_u64().unwrap()); let (hours, minutes) = convert_runtime(movie.runtime);
let imdb_rating = movie let imdb_rating = movie
.ratings .ratings
.imdb .imdb
@@ -218,19 +218,19 @@ pub fn draw_collection_details<B: Backend>(
let imdb_rating = if imdb_rating == 0.0 { let imdb_rating = if imdb_rating == 0.0 {
String::new() String::new()
} else { } else {
format!("{:.1}", imdb_rating) format!("{imdb_rating:.1}")
}; };
let rotten_tomatoes_rating = if rotten_tomatoes_rating == 0 { let rotten_tomatoes_rating = if rotten_tomatoes_rating == 0 {
String::new() String::new()
} else { } else {
format!("{}%", rotten_tomatoes_rating) format!("{rotten_tomatoes_rating}%")
}; };
Row::new(vec![ Row::new(vec![
Cell::from(in_library), Cell::from(in_library),
Cell::from(movie.title.to_string()), Cell::from(movie.title.to_string()),
Cell::from(movie.year.as_u64().unwrap().to_string()), Cell::from(movie.year.to_string()),
Cell::from(format!("{}h {}m", hours, minutes)), Cell::from(format!("{hours}h {minutes}m")),
Cell::from(imdb_rating), Cell::from(imdb_rating),
Cell::from(rotten_tomatoes_rating), Cell::from(rotten_tomatoes_rating),
Cell::from(movie.genres.join(", ")), Cell::from(movie.genres.join(", ")),
@@ -24,7 +24,7 @@ use crate::ui::{
#[path = "edit_collection_ui_tests.rs"] #[path = "edit_collection_ui_tests.rs"]
mod edit_collection_ui_tests; mod edit_collection_ui_tests;
pub(super) struct EditCollectionUi {} pub(super) struct EditCollectionUi;
impl DrawUi for EditCollectionUi { impl DrawUi for EditCollectionUi {
fn accepts(route: Route) -> bool { fn accepts(route: Route) -> bool {
@@ -126,7 +126,7 @@ fn draw_edit_collection_confirmation_prompt<B: Backend>(
.unwrap_or_default(), .unwrap_or_default(),
) )
}; };
let title = format!("Edit - {}", collection_title); let title = format!("Edit - {collection_title}");
let yes_no_value = app.data.radarr_data.prompt_confirm; let yes_no_value = app.data.radarr_data.prompt_confirm;
let selected_block = app.data.radarr_data.selected_block.get_active_block(); let selected_block = app.data.radarr_data.selected_block.get_active_block();
let highlight_yes_no = selected_block == &ActiveRadarrBlock::EditCollectionConfirmPrompt; let highlight_yes_no = selected_block == &ActiveRadarrBlock::EditCollectionConfirmPrompt;
+2 -2
View File
@@ -23,7 +23,7 @@ mod collection_details_ui;
mod collections_ui_tests; mod collections_ui_tests;
mod edit_collection_ui; mod edit_collection_ui;
pub(super) struct CollectionsUi {} pub(super) struct CollectionsUi;
impl DrawUi for CollectionsUi { impl DrawUi for CollectionsUi {
fn accepts(route: Route) -> bool { fn accepts(route: Route) -> bool {
@@ -162,7 +162,7 @@ pub(super) fn draw_collections<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'
Cell::from(collection.root_folder_path.clone().unwrap_or_default()), Cell::from(collection.root_folder_path.clone().unwrap_or_default()),
Cell::from( Cell::from(
quality_profile_map quality_profile_map
.get_by_left(&collection.quality_profile_id.as_u64().unwrap()) .get_by_left(&collection.quality_profile_id)
.unwrap() .unwrap()
.to_owned(), .to_owned(),
), ),
+4 -4
View File
@@ -15,7 +15,7 @@ use crate::utils::convert_to_gb;
#[path = "downloads_ui_tests.rs"] #[path = "downloads_ui_tests.rs"]
mod downloads_ui_tests; mod downloads_ui_tests;
pub(super) struct DownloadsUi {} pub(super) struct DownloadsUi;
impl DrawUi for DownloadsUi { impl DrawUi for DownloadsUi {
fn accepts(route: Route) -> bool { fn accepts(route: Route) -> bool {
@@ -105,13 +105,13 @@ fn draw_downloads<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rec
); );
} }
let percent = 1f64 - (sizeleft.as_f64().unwrap() / size.as_f64().unwrap()); let percent = 1f64 - (*sizeleft as f64 / *size as f64);
let file_size: f64 = convert_to_gb(size.as_u64().unwrap()); let file_size: f64 = convert_to_gb(*size);
Row::new(vec![ Row::new(vec![
Cell::from(title.to_owned()), Cell::from(title.to_owned()),
Cell::from(format!("{:.0}%", percent * 100.0)), Cell::from(format!("{:.0}%", percent * 100.0)),
Cell::from(format!("{:.2} GB", file_size)), Cell::from(format!("{file_size:.2} GB")),
Cell::from( Cell::from(
output_path output_path
.as_ref() .as_ref()
@@ -21,7 +21,7 @@ use crate::ui::{
#[path = "indexer_settings_ui_tests.rs"] #[path = "indexer_settings_ui_tests.rs"]
mod indexer_settings_ui_tests; mod indexer_settings_ui_tests;
pub(super) struct IndexerSettingsUi {} pub(super) struct IndexerSettingsUi;
impl DrawUi for IndexerSettingsUi { impl DrawUi for IndexerSettingsUi {
fn accepts(route: Route) -> bool { fn accepts(route: Route) -> bool {
+2 -2
View File
@@ -18,7 +18,7 @@ mod indexer_settings_ui;
#[path = "indexers_ui_tests.rs"] #[path = "indexers_ui_tests.rs"]
mod indexers_ui_tests; mod indexers_ui_tests;
pub(super) struct IndexersUi {} pub(super) struct IndexersUi;
impl DrawUi for IndexersUi { impl DrawUi for IndexersUi {
fn accepts(route: Route) -> bool { fn accepts(route: Route) -> bool {
@@ -116,7 +116,7 @@ fn draw_indexers<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect
Cell::from(rss), Cell::from(rss),
Cell::from(automatic_search), Cell::from(automatic_search),
Cell::from(interactive_search), Cell::from(interactive_search),
Cell::from(priority.as_u64().unwrap().to_string()), Cell::from(priority.to_string()),
]) ])
.style(style_primary()) .style(style_primary())
}, },
+7 -7
View File
@@ -29,7 +29,7 @@ use crate::App;
#[path = "add_movie_ui_tests.rs"] #[path = "add_movie_ui_tests.rs"]
mod add_movie_ui_tests; mod add_movie_ui_tests;
pub(super) struct AddMovieUi {} pub(super) struct AddMovieUi;
impl DrawUi for AddMovieUi { impl DrawUi for AddMovieUi {
fn accepts(route: Route) -> bool { fn accepts(route: Route) -> bool {
@@ -201,7 +201,7 @@ fn draw_add_movie_search<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, ar
help: None, help: None,
}, },
|movie| { |movie| {
let (hours, minutes) = convert_runtime(movie.runtime.as_u64().unwrap()); let (hours, minutes) = convert_runtime(movie.runtime);
let imdb_rating = movie let imdb_rating = movie
.ratings .ratings
.imdb .imdb
@@ -221,12 +221,12 @@ fn draw_add_movie_search<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, ar
let imdb_rating = if imdb_rating == 0.0 { let imdb_rating = if imdb_rating == 0.0 {
String::new() String::new()
} else { } else {
format!("{:.1}", imdb_rating) format!("{imdb_rating:.1}")
}; };
let rotten_tomatoes_rating = if rotten_tomatoes_rating == 0 { let rotten_tomatoes_rating = if rotten_tomatoes_rating == 0 {
String::new() String::new()
} else { } else {
format!("{}%", rotten_tomatoes_rating) format!("{rotten_tomatoes_rating}%")
}; };
let in_library = if app let in_library = if app
.data .data
@@ -250,8 +250,8 @@ fn draw_add_movie_search<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, ar
Row::new(vec![ Row::new(vec![
Cell::from(in_library), Cell::from(in_library),
Cell::from(movie.title.to_string()), Cell::from(movie.title.to_string()),
Cell::from(movie.year.as_u64().unwrap().to_string()), Cell::from(movie.year.to_string()),
Cell::from(format!("{}h {}m", hours, minutes)), Cell::from(format!("{hours}h {minutes}m")),
Cell::from(imdb_rating), Cell::from(imdb_rating),
Cell::from(rotten_tomatoes_rating), Cell::from(rotten_tomatoes_rating),
Cell::from(movie.genres.join(", ")), Cell::from(movie.genres.join(", ")),
@@ -368,7 +368,7 @@ fn draw_confirmation_prompt<B: Backend>(
.clone(), .clone(),
) )
}; };
let title = format!("Add Movie - {}", movie_title); let title = format!("Add Movie - {movie_title}");
let prompt = movie_overview; let prompt = movie_overview;
let yes_no_value = app.data.radarr_data.prompt_confirm; let yes_no_value = app.data.radarr_data.prompt_confirm;
let selected_block = app.data.radarr_data.selected_block.get_active_block(); let selected_block = app.data.radarr_data.selected_block.get_active_block();
+1 -1
View File
@@ -12,7 +12,7 @@ use crate::ui::{draw_prompt_box_with_checkboxes, draw_prompt_popup_over, DrawUi}
#[path = "delete_movie_ui_tests.rs"] #[path = "delete_movie_ui_tests.rs"]
mod delete_movie_ui_tests; mod delete_movie_ui_tests;
pub(super) struct DeleteMovieUi {} pub(super) struct DeleteMovieUi;
impl DrawUi for DeleteMovieUi { impl DrawUi for DeleteMovieUi {
fn accepts(route: Route) -> bool { fn accepts(route: Route) -> bool {
+2 -2
View File
@@ -25,7 +25,7 @@ use crate::ui::{
#[path = "edit_movie_ui_tests.rs"] #[path = "edit_movie_ui_tests.rs"]
mod edit_movie_ui_tests; mod edit_movie_ui_tests;
pub(super) struct EditMovieUi {} pub(super) struct EditMovieUi;
impl DrawUi for EditMovieUi { impl DrawUi for EditMovieUi {
fn accepts(route: Route) -> bool { fn accepts(route: Route) -> bool {
@@ -118,7 +118,7 @@ fn draw_edit_movie_confirmation_prompt<B: Backend>(
.clone(), .clone(),
) )
}; };
let title = format!("Edit - {}", movie_title); let title = format!("Edit - {movie_title}");
let yes_no_value = app.data.radarr_data.prompt_confirm; let yes_no_value = app.data.radarr_data.prompt_confirm;
let selected_block = app.data.radarr_data.selected_block.get_active_block(); let selected_block = app.data.radarr_data.selected_block.get_active_block();
let highlight_yes_no = selected_block == &ActiveRadarrBlock::EditMovieConfirmPrompt; let highlight_yes_no = selected_block == &ActiveRadarrBlock::EditMovieConfirmPrompt;
+8 -8
View File
@@ -28,7 +28,7 @@ mod movie_details_ui;
#[path = "library_ui_tests.rs"] #[path = "library_ui_tests.rs"]
mod library_ui_tests; mod library_ui_tests;
pub(super) struct LibraryUi {} pub(super) struct LibraryUi;
impl DrawUi for LibraryUi { impl DrawUi for LibraryUi {
fn accepts(route: Route) -> bool { fn accepts(route: Route) -> bool {
@@ -168,11 +168,11 @@ pub(super) fn draw_library<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count % app.ticks_until_scroll == 0,
); );
let monitored = if movie.monitored { "🏷" } else { "" }; let monitored = if movie.monitored { "🏷" } else { "" };
let (hours, minutes) = convert_runtime(movie.runtime.as_u64().unwrap()); let (hours, minutes) = convert_runtime(movie.runtime);
let file_size: f64 = convert_to_gb(movie.size_on_disk.as_u64().unwrap()); let file_size: f64 = convert_to_gb(movie.size_on_disk);
let certification = movie.certification.clone().unwrap_or_else(|| "".to_owned()); let certification = movie.certification.clone().unwrap_or_default();
let quality_profile = quality_profile_map let quality_profile = quality_profile_map
.get_by_left(&movie.quality_profile_id.as_u64().unwrap()) .get_by_left(&movie.quality_profile_id)
.unwrap() .unwrap()
.to_owned(); .to_owned();
let tags = movie let tags = movie
@@ -180,7 +180,7 @@ pub(super) fn draw_library<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>,
.iter() .iter()
.map(|tag_id| { .map(|tag_id| {
tags_map tags_map
.get_by_left(&tag_id.as_u64().unwrap()) .get_by_left(&tag_id.as_i64().unwrap())
.unwrap() .unwrap()
.clone() .clone()
}) })
@@ -191,10 +191,10 @@ pub(super) fn draw_library<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>,
Cell::from(movie.title.to_string()), Cell::from(movie.title.to_string()),
Cell::from(movie.year.to_string()), Cell::from(movie.year.to_string()),
Cell::from(movie.studio.to_string()), Cell::from(movie.studio.to_string()),
Cell::from(format!("{}h {}m", hours, minutes)), Cell::from(format!("{hours}h {minutes}m")),
Cell::from(certification), Cell::from(certification),
Cell::from(movie.original_language.name.to_owned()), Cell::from(movie.original_language.name.to_owned()),
Cell::from(format!("{:.2} GB", file_size)), Cell::from(format!("{file_size:.2} GB")),
Cell::from(quality_profile), Cell::from(quality_profile),
Cell::from(monitored.to_owned()), Cell::from(monitored.to_owned()),
Cell::from(tags), Cell::from(tags),
+6 -6
View File
@@ -28,7 +28,7 @@ use crate::utils::convert_to_gb;
#[path = "movie_details_ui_tests.rs"] #[path = "movie_details_ui_tests.rs"]
mod movie_details_ui_tests; mod movie_details_ui_tests;
pub(super) struct MovieDetailsUi {} pub(super) struct MovieDetailsUi;
impl DrawUi for MovieDetailsUi { impl DrawUi for MovieDetailsUi {
fn accepts(route: Route) -> bool { fn accepts(route: Route) -> bool {
@@ -504,21 +504,21 @@ fn draw_movie_releases<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, cont
quality, quality,
.. ..
} = release; } = release;
let age = format!("{} days", age.as_u64().unwrap_or(0)); let age = format!("{age} days");
title.scroll_left_or_reset( title.scroll_left_or_reset(
get_width_from_percentage(content_area, 30), get_width_from_percentage(content_area, 30),
current_selection == *release current_selection == *release
&& current_route != ActiveRadarrBlock::ManualSearchConfirmPrompt.into(), && current_route != ActiveRadarrBlock::ManualSearchConfirmPrompt.into(),
app.tick_count % app.ticks_until_scroll == 0, app.tick_count % app.ticks_until_scroll == 0,
); );
let size = convert_to_gb(size.as_u64().unwrap()); let size = convert_to_gb(*size);
let rejected_str = if *rejected { "" } else { "" }; let rejected_str = if *rejected { "" } else { "" };
let peers = if seeders.is_none() || leechers.is_none() { let peers = if seeders.is_none() || leechers.is_none() {
Text::default() Text::default()
} else { } else {
let seeders = seeders.clone().unwrap().as_u64().unwrap(); let seeders = seeders.clone().unwrap().as_u64().unwrap();
let leechers = leechers.clone().unwrap().as_u64().unwrap(); let leechers = leechers.clone().unwrap().as_u64().unwrap();
let mut text = Text::from(format!("{} / {}", seeders, leechers)); let mut text = Text::from(format!("{seeders} / {leechers}"));
text.patch_style(determine_peer_style(seeders, leechers)); text.patch_style(determine_peer_style(seeders, leechers));
text text
@@ -537,7 +537,7 @@ fn draw_movie_releases<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, cont
Cell::from(rejected_str), Cell::from(rejected_str),
Cell::from(title.to_string()), Cell::from(title.to_string()),
Cell::from(indexer.clone()), Cell::from(indexer.clone()),
Cell::from(format!("{:.1} GB", size)), Cell::from(format!("{size:.1} GB")),
Cell::from(peers), Cell::from(peers),
Cell::from(language), Cell::from(language),
Cell::from(quality), Cell::from(quality),
@@ -589,7 +589,7 @@ fn draw_manual_search_confirm_prompt<B: Backend>(
.clone() .clone()
.unwrap_or_default() .unwrap_or_default()
.iter() .iter()
.map(|item| Line::from(vec![Span::styled(format!("{}", item), style_primary())])) .map(|item| Line::from(vec![Span::styled(format!("{item}"), style_primary())]))
.collect::<Vec<Line<'_>>>(); .collect::<Vec<Line<'_>>>();
lines_vec.append(&mut rejections_spans); lines_vec.append(&mut rejections_spans);
+7 -11
View File
@@ -41,7 +41,7 @@ mod system;
#[path = "radarr_ui_tests.rs"] #[path = "radarr_ui_tests.rs"]
mod radarr_ui_tests; mod radarr_ui_tests;
pub(super) struct RadarrUi {} pub(super) struct RadarrUi;
impl DrawUi for RadarrUi { impl DrawUi for RadarrUi {
fn accepts(route: Route) -> bool { fn accepts(route: Route) -> bool {
@@ -119,11 +119,7 @@ fn draw_stats_context<B: Backend>(f: &mut Frame<'_, B>, app: &App<'_>, area: Rec
let seconds = (hour_difference - Duration::minutes(minutes)).num_seconds(); let seconds = (hour_difference - Duration::minutes(minutes)).num_seconds();
let uptime_paragraph = Paragraph::new(Text::from(format!( let uptime_paragraph = Paragraph::new(Text::from(format!(
"Uptime: {}d {:0width$}:{:0width$}:{:0width$}", "Uptime: {days}d {hours:0width$}:{minutes:0width$}:{seconds:0width$}",
days,
hours,
minutes,
seconds,
width = 2 width = 2
))) )))
.block(borderless_block()) .block(borderless_block())
@@ -144,10 +140,10 @@ fn draw_stats_context<B: Backend>(f: &mut Frame<'_, B>, app: &App<'_>, area: Rec
total_space, total_space,
} = &disk_space_vec[i]; } = &disk_space_vec[i];
let title = format!("Disk {}", i + 1); let title = format!("Disk {}", i + 1);
let ratio = if total_space.as_u64().unwrap() == 0 { let ratio = if *total_space == 0 {
0f64 0f64
} else { } else {
1f64 - (free_space.as_u64().unwrap() as f64 / total_space.as_u64().unwrap() as f64) 1f64 - (*free_space as f64 / *total_space as f64)
}; };
let space_gauge = line_gauge_with_label(title.as_str(), ratio); let space_gauge = line_gauge_with_label(title.as_str(), ratio);
@@ -161,8 +157,8 @@ fn draw_stats_context<B: Backend>(f: &mut Frame<'_, B>, app: &App<'_>, area: Rec
let RootFolder { let RootFolder {
path, free_space, .. path, free_space, ..
} = &root_folders.items[i]; } = &root_folders.items[i];
let space: f64 = convert_to_gb(free_space.as_u64().unwrap()); let space: f64 = convert_to_gb(*free_space);
let root_folder_space = Paragraph::new(format!("{}: {:.2} GB free", path.to_owned(), space)) let root_folder_space = Paragraph::new(format!("{path}: {space:.2} GB free"))
.block(borderless_block()) .block(borderless_block())
.style(style_default()); .style(style_default());
@@ -193,7 +189,7 @@ fn draw_downloads_context<B: Backend>(f: &mut Frame<'_, B>, app: &App<'_>, area:
size, size,
.. ..
} = &downloads_vec[i]; } = &downloads_vec[i];
let percent = 1f64 - (sizeleft.as_f64().unwrap() / size.as_f64().unwrap()); let percent = 1f64 - (*sizeleft as f64 / *size as f64);
let download_gauge = line_gauge_with_title(title, percent); let download_gauge = line_gauge_with_title(title, percent);
f.render_widget(download_gauge, chunks[i]); f.render_widget(download_gauge, chunks[i]);
+6 -6
View File
@@ -22,23 +22,23 @@ pub(super) fn convert_to_minutes_hours_days(time: i64) -> String {
if time == 0 { if time == 0 {
"now".to_owned() "now".to_owned()
} else if time == 1 { } else if time == 1 {
format!("{} minute", time) format!("{time} minute")
} else { } else {
format!("{} minutes", time) format!("{time} minutes")
} }
} else if time / 60 < 24 { } else if time / 60 < 24 {
let hours = time / 60; let hours = time / 60;
if hours == 1 { if hours == 1 {
format!("{} hour", hours) format!("{hours} hour")
} else { } else {
format!("{} hours", hours) format!("{hours} hours")
} }
} else { } else {
let days = time / (60 * 24); let days = time / (60 * 24);
if days == 1 { if days == 1 {
format!("{} day", days) format!("{days} day")
} else { } else {
format!("{} days", days) format!("{days} days")
} }
} }
} }
+3 -3
View File
@@ -18,7 +18,7 @@ use crate::utils::convert_to_gb;
#[path = "root_folders_ui_tests.rs"] #[path = "root_folders_ui_tests.rs"]
mod root_folders_ui_tests; mod root_folders_ui_tests;
pub(super) struct RootFoldersUi {} pub(super) struct RootFoldersUi;
impl DrawUi for RootFoldersUi { impl DrawUi for RootFoldersUi {
fn accepts(route: Route) -> bool { fn accepts(route: Route) -> bool {
@@ -83,11 +83,11 @@ fn draw_root_folders<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, area:
.. ..
} = root_folders; } = root_folders;
let space: f64 = convert_to_gb(free_space.as_u64().unwrap()); let space: f64 = convert_to_gb(*free_space);
Row::new(vec![ Row::new(vec![
Cell::from(path.to_owned()), Cell::from(path.to_owned()),
Cell::from(format!("{:.2} GB", space)), Cell::from(format!("{space:.2} GB")),
Cell::from( Cell::from(
unmapped_folders unmapped_folders
.as_ref() .as_ref()
+5 -5
View File
@@ -51,7 +51,7 @@ pub(super) const TASK_TABLE_CONSTRAINTS: [Constraint; 5] = [
Constraint::Percentage(22), Constraint::Percentage(22),
]; ];
pub(super) struct SystemUi {} pub(super) struct SystemUi;
impl DrawUi for SystemUi { impl DrawUi for SystemUi {
fn accepts(route: Route) -> bool { fn accepts(route: Route) -> bool {
@@ -151,7 +151,7 @@ pub(super) fn draw_queued_events<B: Backend>(f: &mut Frame<'_, B>, app: &mut App
|event| { |event| {
let queued = convert_to_minutes_hours_days(Utc::now().sub(event.queued).num_minutes()); let queued = convert_to_minutes_hours_days(Utc::now().sub(event.queued).num_minutes());
let queued_string = if queued != "now" { let queued_string = if queued != "now" {
format!("{} ago", queued) format!("{queued} ago")
} else { } else {
queued queued
}; };
@@ -160,7 +160,7 @@ pub(super) fn draw_queued_events<B: Backend>(f: &mut Frame<'_, B>, app: &mut App
convert_to_minutes_hours_days(Utc::now().sub(event.started.unwrap()).num_minutes()); convert_to_minutes_hours_days(Utc::now().sub(event.started.unwrap()).num_minutes());
if started != "now" { if started != "now" {
format!("{} ago", started) format!("{started} ago")
} else { } else {
started started
} }
@@ -237,14 +237,14 @@ pub(super) struct TaskProps {
} }
pub(super) fn extract_task_props(task: &Task) -> TaskProps { pub(super) fn extract_task_props(task: &Task) -> TaskProps {
let interval = convert_to_minutes_hours_days(*task.interval.as_i64().as_ref().unwrap()); let interval = convert_to_minutes_hours_days(task.interval);
let last_duration = &task.last_duration[..8]; let last_duration = &task.last_duration[..8];
let next_execution = let next_execution =
convert_to_minutes_hours_days((task.next_execution - Utc::now()).num_minutes()); convert_to_minutes_hours_days((task.next_execution - Utc::now()).num_minutes());
let last_execution = let last_execution =
convert_to_minutes_hours_days((Utc::now() - task.last_execution).num_minutes()); convert_to_minutes_hours_days((Utc::now() - task.last_execution).num_minutes());
let last_execution_string = if last_execution != "now" { let last_execution_string = if last_execution != "now" {
format!("{} ago", last_execution) format!("{last_execution} ago")
} else { } else {
last_execution last_execution
}; };
+1 -1
View File
@@ -24,7 +24,7 @@ use crate::ui::{
#[path = "system_details_ui_tests.rs"] #[path = "system_details_ui_tests.rs"]
mod system_details_ui_tests; mod system_details_ui_tests;
pub(super) struct SystemDetailsUi {} pub(super) struct SystemDetailsUi;
impl DrawUi for SystemDetailsUi { impl DrawUi for SystemDetailsUi {
fn accepts(route: Route) -> bool { fn accepts(route: Route) -> bool {
+2 -2
View File
@@ -206,7 +206,7 @@ pub fn style_block_highlight(is_selected: bool) -> Style {
} }
pub fn title_style(title: &str) -> Span<'_> { pub fn title_style(title: &str) -> Span<'_> {
Span::styled(format!(" {} ", title), style_bold()) Span::styled(format!(" {title} "), style_bold())
} }
pub fn title_block(title: &str) -> Block<'_> { pub fn title_block(title: &str) -> Block<'_> {
@@ -265,7 +265,7 @@ pub fn line_gauge_with_label(title: &str, ratio: f64) -> LineGauge<'_> {
.gauge_style(Style::default().fg(COLOR_CYAN)) .gauge_style(Style::default().fg(COLOR_CYAN))
.line_set(symbols::line::THICK) .line_set(symbols::line::THICK)
.ratio(ratio) .ratio(ratio)
.label(Line::from(format!("{}: {:.0}%", title, ratio * 100.0))) .label(Line::from(format!("{title}: {:.0}%", ratio * 100.0)))
} }
pub fn show_cursor<B: Backend>(f: &mut Frame<'_, B>, area: Rect, offset: usize, string: &str) { pub fn show_cursor<B: Backend>(f: &mut Frame<'_, B>, area: Rect, offset: usize, string: &str) {
+2 -2
View File
@@ -27,11 +27,11 @@ pub fn init_logging_config() -> log4rs::Config {
.unwrap() .unwrap()
} }
pub fn convert_to_gb(bytes: u64) -> f64 { pub fn convert_to_gb(bytes: i64) -> f64 {
bytes as f64 / 1024f64.powi(3) bytes as f64 / 1024f64.powi(3)
} }
pub fn convert_runtime(runtime: u64) -> (u64, u64) { pub fn convert_runtime(runtime: i64) -> (i64, i64) {
let hours = runtime / 60; let hours = runtime / 60;
let minutes = runtime % 60; let minutes = runtime % 60;