fix: Wrapped all Sonarr use of Language with Option to fix the 'null' array issue in the new Sonarr API

This commit is contained in:
2025-12-13 13:06:33 -07:00
parent 9599ac28ca
commit 9c1a9cc3c5
17 changed files with 139 additions and 49 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
use std::sync::Arc;
use anyhow::Result;
use clap::{command, Subcommand};
use clap::Subcommand;
use clap_complete::Shell;
use radarr::{RadarrCliHandler, RadarrCommand};
use sonarr::{SonarrCliHandler, SonarrCommand};
+1 -1
View File
@@ -1,7 +1,7 @@
use std::sync::Arc;
use anyhow::Result;
use clap::{arg, command, ArgAction, Subcommand};
use clap::{ArgAction, Subcommand};
use tokio::sync::Mutex;
use super::RadarrCommand;
+1 -1
View File
@@ -1,7 +1,7 @@
use std::sync::Arc;
use anyhow::Result;
use clap::{command, Subcommand};
use clap::Subcommand;
use tokio::sync::Mutex;
use crate::{
+1 -1
View File
@@ -1,7 +1,7 @@
use std::sync::Arc;
use anyhow::Result;
use clap::{command, Subcommand};
use clap::Subcommand;
use tokio::sync::Mutex;
use crate::{
@@ -445,13 +445,25 @@ mod tests {
let a_languages = a
.languages
.iter()
.map(|lang| lang.name.to_lowercase())
.map(|lang| {
lang
.as_ref()
.unwrap_or(&Default::default())
.name
.to_lowercase()
})
.collect::<Vec<String>>()
.join(", ");
let b_languages = b
.languages
.iter()
.map(|lang| lang.name.to_lowercase())
.map(|lang| {
lang
.as_ref()
.unwrap_or(&Default::default())
.name
.to_lowercase()
})
.collect::<Vec<String>>()
.join(", ");
@@ -607,10 +619,10 @@ mod tests {
BlocklistItem {
id: 3,
source_title: "test 1".to_owned(),
languages: vec![Language {
languages: vec![Some(Language {
id: 1,
name: "telgu".to_owned(),
}],
})],
quality: QualityWrapper {
quality: Quality {
name: "HD - 1080p".to_owned(),
@@ -623,10 +635,10 @@ mod tests {
BlocklistItem {
id: 2,
source_title: "test 2".to_owned(),
languages: vec![Language {
languages: vec![Some(Language {
id: 3,
name: "chinese".to_owned(),
}],
})],
quality: QualityWrapper {
quality: Quality {
name: "SD - 720p".to_owned(),
@@ -639,10 +651,10 @@ mod tests {
BlocklistItem {
id: 1,
source_title: "test 3".to_owned(),
languages: vec![Language {
languages: vec![Some(Language {
id: 1,
name: "english".to_owned(),
}],
})],
quality: QualityWrapper {
quality: Quality {
name: "HD - 1080p".to_owned(),
+14 -2
View File
@@ -210,13 +210,25 @@ fn blocklist_sorting_options() -> Vec<SortOption<BlocklistItem>> {
let a_languages = a
.languages
.iter()
.map(|lang| lang.name.to_lowercase())
.map(|lang| {
lang
.as_ref()
.unwrap_or(&Default::default())
.name
.to_lowercase()
})
.collect::<Vec<String>>()
.join(", ");
let b_languages = b
.languages
.iter()
.map(|lang| lang.name.to_lowercase())
.map(|lang| {
lang
.as_ref()
.unwrap_or(&Default::default())
.name
.to_lowercase()
})
.collect::<Vec<String>>()
.join(", ");
@@ -245,8 +245,19 @@ mod tests {
id: 1,
name: "_".to_owned(),
};
let language_a = &a.languages.first().unwrap_or(&default_language);
let language_b = &b.languages.first().unwrap_or(&default_language);
let default_language_option = Some(default_language.clone());
let language_a = &a
.languages
.first()
.unwrap_or(&default_language_option)
.as_ref()
.unwrap_or(&default_language);
let language_b = &b
.languages
.first()
.unwrap_or(&default_language_option)
.as_ref()
.unwrap_or(&default_language);
language_a.cmp(language_b)
};
@@ -385,10 +396,10 @@ mod tests {
id: 3,
source_title: "test 1".into(),
event_type: SonarrHistoryEventType::Grabbed,
languages: vec![Language {
languages: vec![Some(Language {
id: 1,
name: "telgu".to_owned(),
}],
})],
quality: QualityWrapper {
quality: Quality {
name: "HD - 1080p".to_owned(),
@@ -401,10 +412,10 @@ mod tests {
id: 2,
source_title: "test 2".into(),
event_type: SonarrHistoryEventType::DownloadFolderImported,
languages: vec![Language {
languages: vec![Some(Language {
id: 3,
name: "chinese".to_owned(),
}],
})],
quality: QualityWrapper {
quality: Quality {
name: "SD - 720p".to_owned(),
@@ -417,10 +428,10 @@ mod tests {
id: 1,
source_title: "test 3".into(),
event_type: SonarrHistoryEventType::EpisodeFileDeleted,
languages: vec![Language {
languages: vec![Some(Language {
id: 1,
name: "english".to_owned(),
}],
})],
quality: QualityWrapper {
quality: Quality {
name: "HD - 1080p".to_owned(),
+13 -2
View File
@@ -150,8 +150,19 @@ pub(in crate::handlers::sonarr_handlers) fn history_sorting_options(
id: 1,
name: "_".to_owned(),
};
let language_a = &a.languages.first().unwrap_or(&default_language);
let language_b = &b.languages.first().unwrap_or(&default_language);
let default_language_option = Some(default_language.clone());
let language_a = &a
.languages
.first()
.unwrap_or(&default_language_option)
.as_ref()
.unwrap_or(&default_language);
let language_b = &b
.languages
.first()
.unwrap_or(&default_language_option)
.as_ref()
.unwrap_or(&default_language);
language_a.cmp(language_b)
}),
@@ -515,12 +515,17 @@ pub(in crate::handlers::sonarr_handlers::library) fn releases_sorting_options(
SortOption {
name: "Language",
cmp_fn: Some(|a, b| {
let default_language_vec = vec![Language {
let default_language = Language {
id: 1,
name: "_".to_owned(),
}];
let language_a = &a.languages.as_ref().unwrap_or(&default_language_vec)[0];
let language_b = &b.languages.as_ref().unwrap_or(&default_language_vec)[0];
};
let default_language_vec = vec![Some(default_language.clone())];
let language_a = &a.languages.as_ref().unwrap_or(&default_language_vec)[0]
.as_ref()
.unwrap_or(&default_language);
let language_b = &b.languages.as_ref().unwrap_or(&default_language_vec)[0]
.as_ref()
.unwrap_or(&default_language);
language_a.cmp(language_b)
}),
@@ -1116,12 +1116,17 @@ mod tests {
#[test]
fn test_releases_sorting_options_language() {
let expected_cmp_fn: fn(&SonarrRelease, &SonarrRelease) -> Ordering = |a, b| {
let default_language_vec = vec![Language {
let default_language = Language {
id: 1,
name: "_".to_owned(),
}];
let language_a = &a.languages.as_ref().unwrap_or(&default_language_vec)[0];
let language_b = &b.languages.as_ref().unwrap_or(&default_language_vec)[0];
};
let default_language_vec = vec![Some(default_language.clone())];
let language_a = a.languages.as_ref().unwrap_or(&default_language_vec)[0]
.as_ref()
.unwrap_or(&default_language);
let language_b = b.languages.as_ref().unwrap_or(&default_language_vec)[0]
.as_ref()
.unwrap_or(&default_language);
language_a.cmp(language_b)
};
@@ -1160,10 +1165,10 @@ mod tests {
size: 1,
rejected: true,
seeders: Some(Number::from(1)),
languages: Some(vec![Language {
languages: Some(vec![Some(Language {
id: 1,
name: "Language A".to_owned(),
}]),
})]),
quality: QualityWrapper {
quality: Quality {
name: "Quality A".to_owned(),
@@ -1179,10 +1184,10 @@ mod tests {
size: 2,
rejected: false,
seeders: Some(Number::from(2)),
languages: Some(vec![Language {
languages: Some(vec![Some(Language {
id: 2,
name: "Language B".to_owned(),
}]),
})]),
quality: QualityWrapper {
quality: Quality {
name: "Quality B".to_owned(),
+3 -3
View File
@@ -84,7 +84,7 @@ pub struct BlocklistItem {
pub series_title: Option<String>,
pub episode_ids: Vec<Number>,
pub source_title: String,
pub languages: Vec<Language>,
pub languages: Vec<Option<Language>>,
pub quality: QualityWrapper,
pub date: DateTime<Utc>,
pub protocol: String,
@@ -509,7 +509,7 @@ pub struct SonarrHistoryItem {
#[serde(deserialize_with = "super::from_i64")]
pub episode_id: i64,
pub quality: QualityWrapper,
pub languages: Vec<Language>,
pub languages: Vec<Option<Language>>,
pub date: DateTime<Utc>,
pub event_type: SonarrHistoryEventType,
pub data: SonarrHistoryData,
@@ -545,7 +545,7 @@ pub struct SonarrRelease {
pub rejections: Option<Vec<String>>,
pub seeders: Option<Number>,
pub leechers: Option<Number>,
pub languages: Option<Vec<Language>>,
pub languages: Option<Vec<Option<Language>>>,
pub quality: QualityWrapper,
pub full_season: bool,
}
@@ -123,7 +123,7 @@ pub(in crate::network::sonarr_network) mod test_utils {
series_title: None,
episode_ids: vec![Number::from(1)],
source_title: "Test Source Title".to_owned(),
languages: vec![language()],
languages: vec![Some(language())],
quality: quality_wrapper(),
date: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()),
protocol: "usenet".to_owned(),
@@ -206,7 +206,7 @@ pub(in crate::network::sonarr_network) mod test_utils {
source_title: "Test source".into(),
episode_id: 1,
quality: quality_wrapper(),
languages: vec![language()],
languages: vec![Some(language())],
date: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()),
event_type: SonarrHistoryEventType::Grabbed,
data: history_data(),
@@ -377,7 +377,7 @@ pub(in crate::network::sonarr_network) mod test_utils {
rejections: Some(rejections()),
seeders: Some(Number::from(2)),
leechers: Some(Number::from(1)),
languages: Some(vec![language()]),
languages: Some(vec![Some(language())]),
quality: quality_wrapper(),
full_season: false,
}
+1 -1
View File
@@ -90,7 +90,7 @@ fn draw_blocklist_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let title = series_title.as_ref().unwrap_or(&String::new()).to_owned();
let languages_string = languages
.iter()
.map(|lang| lang.name.to_owned())
.map(|lang| lang.as_ref().unwrap_or(&Default::default()).name.to_owned())
.collect::<Vec<String>>()
.join(", ");
+8 -1
View File
@@ -1,5 +1,6 @@
use crate::app::App;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, HISTORY_BLOCKS};
use crate::models::servarr_models::Language;
use crate::models::sonarr_models::{SonarrHistoryEventType, SonarrHistoryItem};
use crate::models::Route;
use crate::ui::styles::ManagarrStyle;
@@ -77,7 +78,13 @@ fn draw_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
Cell::from(
languages
.iter()
.map(|language| language.name.to_owned())
.map(|language| {
language
.as_ref()
.unwrap_or(&Language::default())
.name
.to_owned()
})
.collect::<Vec<String>>()
.join(","),
),
+13 -2
View File
@@ -1,5 +1,6 @@
use crate::app::App;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS};
use crate::models::servarr_models::Language;
use crate::models::sonarr_models::{
DownloadRecord, DownloadStatus, Episode, SonarrHistoryEventType, SonarrHistoryItem, SonarrRelease,
};
@@ -280,7 +281,13 @@ fn draw_episode_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
Cell::from(
languages
.iter()
.map(|language| language.name.to_owned())
.map(|language| {
language
.as_ref()
.unwrap_or(&Language::default())
.name
.to_owned()
})
.collect::<Vec<String>>()
.join(","),
),
@@ -441,7 +448,11 @@ fn draw_episode_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
};
let language = if languages.is_some() {
languages.clone().unwrap()[0].name.clone()
languages.clone().unwrap()[0]
.as_ref()
.unwrap_or(&Default::default())
.name
.clone()
} else {
String::new()
};
+12 -2
View File
@@ -276,7 +276,13 @@ fn draw_season_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
Cell::from(
languages
.iter()
.map(|language| language.name.to_owned())
.map(|language| {
language
.as_ref()
.unwrap_or(&Default::default())
.name
.to_owned()
})
.collect::<Vec<String>>()
.join(","),
),
@@ -398,7 +404,11 @@ fn draw_season_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
};
let language = if languages.is_some() {
languages.clone().unwrap()[0].name.clone()
languages.clone().unwrap()[0]
.as_ref()
.unwrap_or(&Default::default())
.name
.clone()
} else {
String::new()
};
@@ -324,7 +324,13 @@ fn draw_series_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
Cell::from(
languages
.iter()
.map(|language| language.name.to_owned())
.map(|language| {
language
.as_ref()
.unwrap_or(&Default::default())
.name
.to_owned()
})
.collect::<Vec<String>>()
.join(","),
),