Merge remote-tracking branch 'origin/main' into test-refactoring
# Conflicts: # Cargo.lock # src/cli/mod.rs # src/cli/radarr/add_command_handler.rs # src/cli/radarr/get_command_handler.rs # src/cli/radarr/list_command_handler.rs
This commit is contained in:
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## v0.6.3 (2025-12-13)
|
||||
|
||||
### Fix
|
||||
|
||||
- Wrapped all Sonarr use of Language with Option to fix the 'null' array issue in the new Sonarr API
|
||||
|
||||
## v0.6.2 (2025-12-12)
|
||||
|
||||
### Fix
|
||||
|
||||
- Fixed breaking Sonarr Episode file API calls after recent Sonarr API update
|
||||
|
||||
### Refactor
|
||||
|
||||
- Replaced all modulo usages of tick_until_poll with is_multiple_of
|
||||
|
||||
## v0.6.1 (2025-09-02)
|
||||
|
||||
### Fix
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "managarr"
|
||||
version = "0.6.1"
|
||||
version = "0.6.3"
|
||||
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||
description = "A TUI and CLI to manage your Servarrs"
|
||||
keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"]
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Subcommand, command};
|
||||
use clap::{command, Subcommand};
|
||||
use clap_complete::Shell;
|
||||
use radarr::{RadarrCliHandler, RadarrCommand};
|
||||
use sonarr::{SonarrCliHandler, SonarrCommand};
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
models::radarr_models::{AddMovieBody, AddMovieOptions, MinimumAvailability, MovieMonitor},
|
||||
network::{NetworkTrait, radarr_network::RadarrEvent},
|
||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Subcommand, command};
|
||||
use clap::{command, Subcommand};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
network::{NetworkTrait, radarr_network::RadarrEvent},
|
||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
||||
};
|
||||
|
||||
use super::RadarrCommand;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Subcommand, command};
|
||||
use clap::{command, Subcommand};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
network::{NetworkTrait, radarr_network::RadarrEvent},
|
||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
||||
};
|
||||
|
||||
use super::RadarrCommand;
|
||||
|
||||
@@ -439,13 +439,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(", ");
|
||||
|
||||
@@ -601,10 +613,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(),
|
||||
@@ -617,10 +629,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(),
|
||||
@@ -633,10 +645,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(),
|
||||
|
||||
@@ -214,13 +214,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(", ");
|
||||
|
||||
|
||||
@@ -246,8 +246,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)
|
||||
};
|
||||
@@ -386,10 +397,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(),
|
||||
@@ -402,10 +413,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(),
|
||||
@@ -418,10 +429,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(),
|
||||
|
||||
@@ -154,8 +154,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)
|
||||
}),
|
||||
|
||||
@@ -518,12 +518,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)
|
||||
}),
|
||||
|
||||
@@ -1097,12 +1097,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)
|
||||
};
|
||||
@@ -1141,10 +1146,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(),
|
||||
@@ -1160,10 +1165,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(),
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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(", ");
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::app::App;
|
||||
use crate::models::Route;
|
||||
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::ui::DrawUi;
|
||||
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(","),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::app::App;
|
||||
use crate::models::Route;
|
||||
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,
|
||||
};
|
||||
@@ -289,7 +290,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(","),
|
||||
),
|
||||
@@ -450,7 +457,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()
|
||||
};
|
||||
|
||||
@@ -280,7 +280,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(","),
|
||||
),
|
||||
@@ -402,7 +408,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(","),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user