Compare commits

..

6 Commits

28 changed files with 694 additions and 513 deletions
+2 -2
View File
@@ -50,8 +50,8 @@ jobs:
- name: Install Commitizen - name: Install Commitizen
run: | run: |
python -m pip install --upgrade pip python3 -m pip install --upgrade pip
pip install commitizen pip3 install commitizen==4.8.3
npm install -g conventional-changelog-cli npm install -g conventional-changelog-cli
- name: Configure Git user - name: Configure Git user
+10
View File
@@ -5,6 +5,16 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 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) ## v0.6.1 (2025-09-02)
### Fix ### Fix
Generated
+645 -483
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "managarr" name = "managarr"
version = "0.6.1" version = "0.6.2"
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"] authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
description = "A TUI and CLI to manage your Servarrs" description = "A TUI and CLI to manage your Servarrs"
keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"] keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"]
+4 -1
View File
@@ -160,7 +160,10 @@ impl App<'_> {
} }
pub async fn on_tick(&mut self) { pub async fn on_tick(&mut self) {
if self.tick_count % self.tick_until_poll == 0 || self.is_routing || self.should_refresh { if self.tick_count.is_multiple_of(self.tick_until_poll)
|| self.is_routing
|| self.should_refresh
{
match self.get_current_route() { match self.get_current_route() {
Route::Radarr(active_radarr_block, _) => self.radarr_on_tick(active_radarr_block).await, Route::Radarr(active_radarr_block, _) => self.radarr_on_tick(active_radarr_block).await,
Route::Sonarr(active_sonarr_block, _) => self.sonarr_on_tick(active_sonarr_block).await, Route::Sonarr(active_sonarr_block, _) => self.sonarr_on_tick(active_sonarr_block).await,
+1 -1
View File
@@ -185,7 +185,7 @@ impl App<'_> {
} }
} }
if self.tick_count % self.tick_until_poll == 0 { if self.tick_count.is_multiple_of(self.tick_until_poll) {
self.refresh_radarr_metadata().await; self.refresh_radarr_metadata().await;
} }
} }
+1 -1
View File
@@ -215,7 +215,7 @@ impl App<'_> {
} }
} }
if self.tick_count % self.tick_until_poll == 0 { if self.tick_count.is_multiple_of(self.tick_until_poll) {
self.refresh_sonarr_metadata().await; self.refresh_sonarr_metadata().await;
} }
} }
@@ -228,7 +228,7 @@ pub(in crate::handlers::sonarr_handlers) mod utils {
path: "/nfs/tv/series/season 1/episode 1.mkv".to_owned(), path: "/nfs/tv/series/season 1/episode 1.mkv".to_owned(),
size: 3543348019, size: 3543348019,
quality: quality_wrapper(), quality: quality_wrapper(),
languages: vec![language()], languages: vec![Some(language())],
date_added: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()), date_added: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()),
media_info: Some(media_info()), media_info: Some(media_info()),
} }
+1 -1
View File
@@ -215,7 +215,7 @@ pub struct EpisodeFile {
pub path: String, pub path: String,
#[serde(deserialize_with = "super::from_i64")] #[serde(deserialize_with = "super::from_i64")]
pub size: i64, pub size: i64,
pub languages: Vec<Language>, pub languages: Vec<Option<Language>>,
pub quality: QualityWrapper, pub quality: QualityWrapper,
pub date_added: DateTime<Utc>, pub date_added: DateTime<Utc>,
pub media_info: Option<MediaInfo>, pub media_info: Option<MediaInfo>,
@@ -297,7 +297,13 @@ impl Network<'_, '_> {
Date Added: {}", Date Added: {}",
file.relative_path, file.relative_path,
file.path, file.path,
file.languages.first().unwrap_or(&Language::default()).name, file
.languages
.first()
.unwrap_or(&Some(Language::default()))
.as_ref()
.unwrap_or(&Language::default())
.name,
file.date_added, file.date_added,
); );
@@ -180,7 +180,7 @@ pub(in crate::network::sonarr_network) mod test_utils {
path: "/nfs/tv/series/season 1/episode 1.mkv".to_owned(), path: "/nfs/tv/series/season 1/episode 1.mkv".to_owned(),
size: 3543348019, size: 3543348019,
quality: quality_wrapper(), quality: quality_wrapper(),
languages: vec![language()], languages: vec![Some(language())],
date_added: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()), date_added: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()),
media_info: Some(media_info()), media_info: Some(media_info()),
} }
+1 -1
View File
@@ -121,7 +121,7 @@ fn draw_error(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
app.error.scroll_left_or_reset( app.error.scroll_left_or_reset(
area.width as usize, area.width as usize,
true, true,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let paragraph = Paragraph::new(Text::from(app.error.to_string().failure())) let paragraph = Paragraph::new(Text::from(app.error.to_string().failure()))
+1 -1
View File
@@ -97,7 +97,7 @@ fn draw_blocklist_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
movie.title.scroll_left_or_reset( movie.title.scroll_left_or_reset(
get_width_from_percentage(area, 20), get_width_from_percentage(area, 20),
current_selection == *blocklist_item, current_selection == *blocklist_item,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let languages_string = languages let languages_string = languages
@@ -90,7 +90,7 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
movie.title.scroll_left_or_reset( movie.title.scroll_left_or_reset(
get_width_from_percentage(table_area, 20), get_width_from_percentage(table_area, 20),
current_selection == *movie, current_selection == *movie,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let (hours, minutes) = convert_runtime(movie.runtime); let (hours, minutes) = convert_runtime(movie.runtime);
let imdb_rating = movie let imdb_rating = movie
+1 -1
View File
@@ -71,7 +71,7 @@ pub(super) fn draw_collections(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
collection.title.scroll_left_or_reset( collection.title.scroll_left_or_reset(
get_width_from_percentage(area, 25), get_width_from_percentage(area, 25),
*collection == current_selection, *collection == current_selection,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let monitored = if collection.monitored { "🏷" } else { "" }; let monitored = if collection.monitored { "🏷" } else { "" };
let search_on_add = if collection.search_on_add { let search_on_add = if collection.search_on_add {
+1 -1
View File
@@ -88,7 +88,7 @@ fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
output_path.as_ref().unwrap().scroll_left_or_reset( output_path.as_ref().unwrap().scroll_left_or_reset(
get_width_from_percentage(area, 18), get_width_from_percentage(area, 18),
current_selection == *download_record, current_selection == *download_record,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
} }
@@ -46,7 +46,7 @@ fn draw_test_all_indexers_test_results(f: &mut Frame<'_>, app: &mut App<'_>, are
result.validation_failures.scroll_left_or_reset( result.validation_failures.scroll_left_or_reset(
get_width_from_percentage(area, 86), get_width_from_percentage(area, 86),
*result == current_selection, *result == current_selection,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let pass_fail = if result.is_valid { "" } else { "" }; let pass_fail = if result.is_valid { "" } else { "" };
let row = Row::new(vec![ let row = Row::new(vec![
+1 -1
View File
@@ -134,7 +134,7 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
movie.title.scroll_left_or_reset( movie.title.scroll_left_or_reset(
get_width_from_percentage(area, 27), get_width_from_percentage(area, 27),
*movie == current_selection, *movie == current_selection,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
Row::new(vec![ Row::new(vec![
+1 -1
View File
@@ -90,7 +90,7 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
movie.title.scroll_left_or_reset( movie.title.scroll_left_or_reset(
get_width_from_percentage(area, 27), get_width_from_percentage(area, 27),
*movie == current_selection, *movie == current_selection,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let monitored = if movie.monitored { "🏷" } else { "" }; let monitored = if movie.monitored { "🏷" } else { "" };
let studio = movie.studio.clone().unwrap_or_default(); let studio = movie.studio.clone().unwrap_or_default();
+2 -2
View File
@@ -241,7 +241,7 @@ fn draw_movie_history(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
movie_history_item.source_title.scroll_left_or_reset( movie_history_item.source_title.scroll_left_or_reset(
get_width_from_percentage(area, 34), get_width_from_percentage(area, 34),
current_selection == *movie_history_item, current_selection == *movie_history_item,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
Row::new(vec![ Row::new(vec![
@@ -393,7 +393,7 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
get_width_from_percentage(area, 30), get_width_from_percentage(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.is_multiple_of(app.ticks_until_scroll),
); );
let size = convert_to_gb(*size); let size = convert_to_gb(*size);
let rejected_str = if *rejected { "" } else { "" }; let rejected_str = if *rejected { "" } else { "" };
+1 -1
View File
@@ -88,7 +88,7 @@ fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
output_path.as_ref().unwrap().scroll_left_or_reset( output_path.as_ref().unwrap().scroll_left_or_reset(
get_width_from_percentage(area, 18), get_width_from_percentage(area, 18),
current_selection == *download_record, current_selection == *download_record,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
} }
+1 -1
View File
@@ -68,7 +68,7 @@ fn draw_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
source_title.scroll_left_or_reset( source_title.scroll_left_or_reset(
get_width_from_percentage(area, 40), get_width_from_percentage(area, 40),
current_selection == *history_item, current_selection == *history_item,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
Row::new(vec![ Row::new(vec![
@@ -44,7 +44,7 @@ fn draw_test_all_indexers_test_results(f: &mut Frame<'_>, app: &mut App<'_>, are
result.validation_failures.scroll_left_or_reset( result.validation_failures.scroll_left_or_reset(
get_width_from_percentage(area, 86), get_width_from_percentage(area, 86),
*result == current_selection, *result == current_selection,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let pass_fail = if result.is_valid { "" } else { "" }; let pass_fail = if result.is_valid { "" } else { "" };
let row = Row::new(vec![ let row = Row::new(vec![
+1 -1
View File
@@ -120,7 +120,7 @@ fn draw_add_series_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
series.title.scroll_left_or_reset( series.title.scroll_left_or_reset(
get_width_from_percentage(area, 27), get_width_from_percentage(area, 27),
*series == current_selection, *series == current_selection,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
Row::new(vec![ Row::new(vec![
@@ -271,7 +271,7 @@ fn draw_episode_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
source_title.scroll_left_or_reset( source_title.scroll_left_or_reset(
get_width_from_percentage(area, 40), get_width_from_percentage(area, 40),
current_selection == *history_item, current_selection == *history_item,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
Row::new(vec![ Row::new(vec![
@@ -415,7 +415,7 @@ fn draw_episode_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
get_width_from_percentage(area, 30), get_width_from_percentage(area, 30),
current_selection == *release current_selection == *release
&& active_sonarr_block != ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt, && active_sonarr_block != ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let size = convert_to_gb(*size); let size = convert_to_gb(*size);
let rejected_str = if *rejected { "" } else { "" }; let rejected_str = if *rejected { "" } else { "" };
+1 -1
View File
@@ -95,7 +95,7 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
series.title.scroll_left_or_reset( series.title.scroll_left_or_reset(
get_width_from_percentage(area, 23), get_width_from_percentage(area, 23),
*series == current_selection, *series == current_selection,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let monitored = if series.monitored { "🏷" } else { "" }; let monitored = if series.monitored { "🏷" } else { "" };
let certification = series.certification.clone().unwrap_or_default(); let certification = series.certification.clone().unwrap_or_default();
@@ -267,7 +267,7 @@ fn draw_season_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
source_title.scroll_left_or_reset( source_title.scroll_left_or_reset(
get_width_from_percentage(area, 40), get_width_from_percentage(area, 40),
current_selection == *history_item, current_selection == *history_item,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
Row::new(vec![ Row::new(vec![
@@ -372,7 +372,7 @@ fn draw_season_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
get_width_from_percentage(area, 30), get_width_from_percentage(area, 30),
current_selection == *release current_selection == *release
&& active_sonarr_block != ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt, && active_sonarr_block != ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let size = convert_to_gb(*size); let size = convert_to_gb(*size);
let rejected_str = if *rejected { "" } else { "" }; let rejected_str = if *rejected { "" } else { "" };
@@ -315,7 +315,7 @@ fn draw_series_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
source_title.scroll_left_or_reset( source_title.scroll_left_or_reset(
get_width_from_percentage(area, 40), get_width_from_percentage(area, 40),
current_selection == *history_item, current_selection == *history_item,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
Row::new(vec![ Row::new(vec![