Compare commits

...

11 Commits

42 changed files with 949 additions and 594 deletions
+91 -13
View File
@@ -8,9 +8,9 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
bump_type: bump_type:
description: "Specify the type of version bump" description: 'Specify the type of version bump'
required: true required: true
default: "patch" default: 'patch'
type: choice type: choice
options: options:
- patch - patch
@@ -46,12 +46,12 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: '3.10'
- 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
@@ -126,12 +126,90 @@ jobs:
echo "No changes to commit (already at $VERSION)" echo "No changes to commit (already at $VERSION)"
fi fi
- name: Bump validate_theme_derive/Cargo.toml version
shell: bash
working-directory: ${{ github.workspace }}/proc_macros/validate_theme_derive
env:
VERSION: ${{ env.version }}
run: |
set -euo pipefail
: "${VERSION:?env.version is empty}"
# Ignore Act's local artifact dir noise
echo artifacts/ >> .git/info/exclude || true
# Edit the version line right after name="managarr"
sed -E -i '
/^[[:space:]]*name[[:space:]]*=[[:space:]]*"managarr"[[:space:]]*$/ {
n
s|^[[:space:]]*version[[:space:]]*=[[:space:]]*"[^"]*"|version = "'"$VERSION"'"|
}
' Cargo.toml
cargo update || true
# Git config that helps in containers (Act)
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git config --global --add safe.directory "$GITHUB_WORKSPACE"
# Debug: show what changed
git status --porcelain
git diff --name-only -- Cargo.toml || true
# Only commit if one of these files actually changed
if ! git diff --quiet -- Cargo.toml; then
# Stage only modifications of already tracked files (won't pick up artifacts/)
git add -u -- Cargo.toml
git commit -m "chore: bump validate_theme_derive Cargo.toml to $VERSION"
else
echo "No changes to commit (already at $VERSION)"
fi
- name: Bump enum_display_style_derive/Cargo.toml version
shell: bash
working-directory: ${{ github.workspace }}/proc_macros/enum_display_style_derive
env:
VERSION: ${{ env.version }}
run: |
set -euo pipefail
: "${VERSION:?env.version is empty}"
# Ignore Act's local artifact dir noise
echo artifacts/ >> .git/info/exclude || true
# Edit the version line right after name="managarr"
sed -E -i '
/^[[:space:]]*name[[:space:]]*=[[:space:]]*"managarr"[[:space:]]*$/ {
n
s|^[[:space:]]*version[[:space:]]*=[[:space:]]*"[^"]*"|version = "'"$VERSION"'"|
}
' Cargo.toml
cargo update || true
# Git config that helps in containers (Act)
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git config --global --add safe.directory "$GITHUB_WORKSPACE"
# Debug: show what changed
git status --porcelain
git diff --name-only -- Cargo.toml || true
# Only commit if one of these files actually changed
if ! git diff --quiet -- Cargo.toml; then
# Stage only modifications of already tracked files (won't pick up artifacts/)
git add -u -- Cargo.toml
git commit -m "chore: bump enum_display_style_derive Cargo.toml to $VERSION"
else
echo "No changes to commit (already at $VERSION)"
fi
- name: Generate changelog for the version bump - name: Generate changelog for the version bump
id: changelog id: changelog
run: | run: |
changelog=$(conventional-changelog -p angular -i CHANGELOG.md -s --from ${{ env.prev_version }} --to ${{ env.version }}) conventional-changelog -p angular -i CHANGELOG.md --from ${{ env.prev_version }} --to v${{ env.version }} > artifacts/changelog.md
echo "$changelog" > artifacts/changelog.md
echo "changelog_body=$(cat artifacts/changelog.md)" >> $GITHUB_ENV
- name: Push changes - name: Push changes
if: env.ACT != 'true' if: env.ACT != 'true'
@@ -153,6 +231,8 @@ jobs:
path: | path: |
Cargo.toml Cargo.toml
Cargo.lock Cargo.lock
proc_macros/validate_theme_derive/Cargo.toml
proc_macros/enum_display_style_derive/Cargo.toml
build-release-artifacts: build-release-artifacts:
name: build-release name: build-release
@@ -333,13 +413,11 @@ jobs:
run: | run: |
release_version="$(cat ./artifacts/release-version)" release_version="$(cat ./artifacts/release-version)"
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
changelog_body="$(cat ./artifacts/changelog.md)"
echo "changelog_body=$(cat artifacts/changelog.md)" >> $GITHUB_ENV
- name: Validate release environment variables - name: Validate release environment variables
run: | run: |
echo "Release version: ${{ env.RELEASE_VERSION }}" echo "Release version: ${{ env.RELEASE_VERSION }}"
echo "Changelog body: ${{ env.changelog_body }}" echo "Changelog body: $(cat artifacts/changelog.md)"
- name: Create a GitHub Release - name: Create a GitHub Release
if: env.ACT != 'true' if: env.ACT != 'true'
@@ -373,8 +451,8 @@ jobs:
artifacts/managarr-armv7-musl.tar.gz artifacts/managarr-armv7-musl.tar.gz
artifacts/managarr-armv7-musl.sha256 artifacts/managarr-armv7-musl.sha256
tag_name: v${{ env.RELEASE_VERSION }} tag_name: v${{ env.RELEASE_VERSION }}
name: "v${{ env.RELEASE_VERSION }}" name: 'v${{ env.RELEASE_VERSION }}'
body: ${{ env.changelog_body }} body_path: artifacts/changelog.md
draft: false draft: false
prerelease: false prerelease: false
+16
View File
@@ -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/), 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.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) ## v0.6.1 (2025-09-02)
### Fix ### Fix
Generated
+663 -501
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.3"
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"]
+1 -1
View File
@@ -1,7 +1,7 @@
# Documentation: https://docs.brew.sh/Formula-Cookbook # Documentation: https://docs.brew.sh/Formula-Cookbook
# https://rubydoc.brew.sh/Formula # https://rubydoc.brew.sh/Formula
class Managarr < Formula class Managarr < Formula
desc "A fast and simple dashboard for Kubernetes written in Rust" desc "Managarr is a TUI and CLI to help you manage your HTPC (Home Theater PC)"
homepage "https://github.com/Dark-Alex-17/managarr" homepage "https://github.com/Dark-Alex-17/managarr"
if OS.mac? and Hardware::CPU.arm? if OS.mac? and Hardware::CPU.arm?
url "https://github.com/Dark-Alex-17/managarr/releases/download/v$version/managarr-macos-arm64.tar.gz" url "https://github.com/Dark-Alex-17/managarr/releases/download/v$version/managarr-macos-arm64.tar.gz"
@@ -1,6 +1,6 @@
[package] [package]
name = "enum_display_style_derive" name = "enum_display_style_derive"
version = "0.1.0" version = "0.6.1"
edition = "2024" edition = "2024"
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"] authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
description = "A proc-macro to derive a `Display` and `FromStr` implementation for enums with a `style` attribute." description = "A proc-macro to derive a `Display` and `FromStr` implementation for enums with a `style` attribute."
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "validate_theme_derive" name = "validate_theme_derive"
version = "0.1.0" version = "0.6.1"
edition = "2024" edition = "2024"
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"] authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
description = "A proc-macro to validate a theme." description = "A proc-macro to validate a theme."
+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;
} }
} }
+1 -1
View File
@@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use clap::{command, Subcommand}; use clap::Subcommand;
use clap_complete::Shell; use clap_complete::Shell;
use radarr::{RadarrCliHandler, RadarrCommand}; use radarr::{RadarrCliHandler, RadarrCommand};
use sonarr::{SonarrCliHandler, SonarrCommand}; use sonarr::{SonarrCliHandler, SonarrCommand};
+1 -1
View File
@@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use clap::{arg, command, ArgAction, Subcommand}; use clap::{ArgAction, Subcommand};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use super::RadarrCommand; use super::RadarrCommand;
+1 -1
View File
@@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use clap::{command, Subcommand}; use clap::Subcommand;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::{ use crate::{
+1 -1
View File
@@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use clap::{command, Subcommand}; use clap::Subcommand;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::{ use crate::{
@@ -445,13 +445,25 @@ mod tests {
let a_languages = a let a_languages = a
.languages .languages
.iter() .iter()
.map(|lang| lang.name.to_lowercase()) .map(|lang| {
lang
.as_ref()
.unwrap_or(&Default::default())
.name
.to_lowercase()
})
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", "); .join(", ");
let b_languages = b let b_languages = b
.languages .languages
.iter() .iter()
.map(|lang| lang.name.to_lowercase()) .map(|lang| {
lang
.as_ref()
.unwrap_or(&Default::default())
.name
.to_lowercase()
})
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", "); .join(", ");
@@ -607,10 +619,10 @@ mod tests {
BlocklistItem { BlocklistItem {
id: 3, id: 3,
source_title: "test 1".to_owned(), source_title: "test 1".to_owned(),
languages: vec![Language { languages: vec![Some(Language {
id: 1, id: 1,
name: "telgu".to_owned(), name: "telgu".to_owned(),
}], })],
quality: QualityWrapper { quality: QualityWrapper {
quality: Quality { quality: Quality {
name: "HD - 1080p".to_owned(), name: "HD - 1080p".to_owned(),
@@ -623,10 +635,10 @@ mod tests {
BlocklistItem { BlocklistItem {
id: 2, id: 2,
source_title: "test 2".to_owned(), source_title: "test 2".to_owned(),
languages: vec![Language { languages: vec![Some(Language {
id: 3, id: 3,
name: "chinese".to_owned(), name: "chinese".to_owned(),
}], })],
quality: QualityWrapper { quality: QualityWrapper {
quality: Quality { quality: Quality {
name: "SD - 720p".to_owned(), name: "SD - 720p".to_owned(),
@@ -639,10 +651,10 @@ mod tests {
BlocklistItem { BlocklistItem {
id: 1, id: 1,
source_title: "test 3".to_owned(), source_title: "test 3".to_owned(),
languages: vec![Language { languages: vec![Some(Language {
id: 1, id: 1,
name: "english".to_owned(), name: "english".to_owned(),
}], })],
quality: QualityWrapper { quality: QualityWrapper {
quality: Quality { quality: Quality {
name: "HD - 1080p".to_owned(), name: "HD - 1080p".to_owned(),
+14 -2
View File
@@ -210,13 +210,25 @@ fn blocklist_sorting_options() -> Vec<SortOption<BlocklistItem>> {
let a_languages = a let a_languages = a
.languages .languages
.iter() .iter()
.map(|lang| lang.name.to_lowercase()) .map(|lang| {
lang
.as_ref()
.unwrap_or(&Default::default())
.name
.to_lowercase()
})
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", "); .join(", ");
let b_languages = b let b_languages = b
.languages .languages
.iter() .iter()
.map(|lang| lang.name.to_lowercase()) .map(|lang| {
lang
.as_ref()
.unwrap_or(&Default::default())
.name
.to_lowercase()
})
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", "); .join(", ");
@@ -245,8 +245,19 @@ mod tests {
id: 1, id: 1,
name: "_".to_owned(), name: "_".to_owned(),
}; };
let language_a = &a.languages.first().unwrap_or(&default_language); let default_language_option = Some(default_language.clone());
let language_b = &b.languages.first().unwrap_or(&default_language); 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) language_a.cmp(language_b)
}; };
@@ -385,10 +396,10 @@ mod tests {
id: 3, id: 3,
source_title: "test 1".into(), source_title: "test 1".into(),
event_type: SonarrHistoryEventType::Grabbed, event_type: SonarrHistoryEventType::Grabbed,
languages: vec![Language { languages: vec![Some(Language {
id: 1, id: 1,
name: "telgu".to_owned(), name: "telgu".to_owned(),
}], })],
quality: QualityWrapper { quality: QualityWrapper {
quality: Quality { quality: Quality {
name: "HD - 1080p".to_owned(), name: "HD - 1080p".to_owned(),
@@ -401,10 +412,10 @@ mod tests {
id: 2, id: 2,
source_title: "test 2".into(), source_title: "test 2".into(),
event_type: SonarrHistoryEventType::DownloadFolderImported, event_type: SonarrHistoryEventType::DownloadFolderImported,
languages: vec![Language { languages: vec![Some(Language {
id: 3, id: 3,
name: "chinese".to_owned(), name: "chinese".to_owned(),
}], })],
quality: QualityWrapper { quality: QualityWrapper {
quality: Quality { quality: Quality {
name: "SD - 720p".to_owned(), name: "SD - 720p".to_owned(),
@@ -417,10 +428,10 @@ mod tests {
id: 1, id: 1,
source_title: "test 3".into(), source_title: "test 3".into(),
event_type: SonarrHistoryEventType::EpisodeFileDeleted, event_type: SonarrHistoryEventType::EpisodeFileDeleted,
languages: vec![Language { languages: vec![Some(Language {
id: 1, id: 1,
name: "english".to_owned(), name: "english".to_owned(),
}], })],
quality: QualityWrapper { quality: QualityWrapper {
quality: Quality { quality: Quality {
name: "HD - 1080p".to_owned(), 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, id: 1,
name: "_".to_owned(), name: "_".to_owned(),
}; };
let language_a = &a.languages.first().unwrap_or(&default_language); let default_language_option = Some(default_language.clone());
let language_b = &b.languages.first().unwrap_or(&default_language); 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) language_a.cmp(language_b)
}), }),
@@ -515,12 +515,17 @@ pub(in crate::handlers::sonarr_handlers::library) fn releases_sorting_options(
SortOption { SortOption {
name: "Language", name: "Language",
cmp_fn: Some(|a, b| { cmp_fn: Some(|a, b| {
let default_language_vec = vec![Language { let default_language = Language {
id: 1, id: 1,
name: "_".to_owned(), name: "_".to_owned(),
}]; };
let language_a = &a.languages.as_ref().unwrap_or(&default_language_vec)[0]; let default_language_vec = vec![Some(default_language.clone())];
let language_b = &b.languages.as_ref().unwrap_or(&default_language_vec)[0]; 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) language_a.cmp(language_b)
}), }),
@@ -1116,12 +1116,17 @@ mod tests {
#[test] #[test]
fn test_releases_sorting_options_language() { fn test_releases_sorting_options_language() {
let expected_cmp_fn: fn(&SonarrRelease, &SonarrRelease) -> Ordering = |a, b| { let expected_cmp_fn: fn(&SonarrRelease, &SonarrRelease) -> Ordering = |a, b| {
let default_language_vec = vec![Language { let default_language = Language {
id: 1, id: 1,
name: "_".to_owned(), name: "_".to_owned(),
}]; };
let language_a = &a.languages.as_ref().unwrap_or(&default_language_vec)[0]; let default_language_vec = vec![Some(default_language.clone())];
let language_b = &b.languages.as_ref().unwrap_or(&default_language_vec)[0]; 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) language_a.cmp(language_b)
}; };
@@ -1160,10 +1165,10 @@ mod tests {
size: 1, size: 1,
rejected: true, rejected: true,
seeders: Some(Number::from(1)), seeders: Some(Number::from(1)),
languages: Some(vec![Language { languages: Some(vec![Some(Language {
id: 1, id: 1,
name: "Language A".to_owned(), name: "Language A".to_owned(),
}]), })]),
quality: QualityWrapper { quality: QualityWrapper {
quality: Quality { quality: Quality {
name: "Quality A".to_owned(), name: "Quality A".to_owned(),
@@ -1179,10 +1184,10 @@ mod tests {
size: 2, size: 2,
rejected: false, rejected: false,
seeders: Some(Number::from(2)), seeders: Some(Number::from(2)),
languages: Some(vec![Language { languages: Some(vec![Some(Language {
id: 2, id: 2,
name: "Language B".to_owned(), name: "Language B".to_owned(),
}]), })]),
quality: QualityWrapper { quality: QualityWrapper {
quality: Quality { quality: Quality {
name: "Quality B".to_owned(), name: "Quality B".to_owned(),
@@ -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()),
} }
+4 -4
View File
@@ -84,7 +84,7 @@ pub struct BlocklistItem {
pub series_title: Option<String>, pub series_title: Option<String>,
pub episode_ids: Vec<Number>, pub episode_ids: Vec<Number>,
pub source_title: String, pub source_title: String,
pub languages: Vec<Language>, pub languages: Vec<Option<Language>>,
pub quality: QualityWrapper, pub quality: QualityWrapper,
pub date: DateTime<Utc>, pub date: DateTime<Utc>,
pub protocol: String, pub protocol: String,
@@ -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>,
@@ -509,7 +509,7 @@ pub struct SonarrHistoryItem {
#[serde(deserialize_with = "super::from_i64")] #[serde(deserialize_with = "super::from_i64")]
pub episode_id: i64, pub episode_id: i64,
pub quality: QualityWrapper, pub quality: QualityWrapper,
pub languages: Vec<Language>, pub languages: Vec<Option<Language>>,
pub date: DateTime<Utc>, pub date: DateTime<Utc>,
pub event_type: SonarrHistoryEventType, pub event_type: SonarrHistoryEventType,
pub data: SonarrHistoryData, pub data: SonarrHistoryData,
@@ -545,7 +545,7 @@ pub struct SonarrRelease {
pub rejections: Option<Vec<String>>, pub rejections: Option<Vec<String>>,
pub seeders: Option<Number>, pub seeders: Option<Number>,
pub leechers: Option<Number>, pub leechers: Option<Number>,
pub languages: Option<Vec<Language>>, pub languages: Option<Vec<Option<Language>>>,
pub quality: QualityWrapper, pub quality: QualityWrapper,
pub full_season: bool, pub full_season: bool,
} }
@@ -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,
); );
@@ -123,7 +123,7 @@ pub(in crate::network::sonarr_network) mod test_utils {
series_title: None, series_title: None,
episode_ids: vec![Number::from(1)], episode_ids: vec![Number::from(1)],
source_title: "Test Source Title".to_owned(), source_title: "Test Source Title".to_owned(),
languages: vec![language()], languages: vec![Some(language())],
quality: quality_wrapper(), quality: quality_wrapper(),
date: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()), date: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()),
protocol: "usenet".to_owned(), protocol: "usenet".to_owned(),
@@ -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()),
} }
@@ -206,7 +206,7 @@ pub(in crate::network::sonarr_network) mod test_utils {
source_title: "Test source".into(), source_title: "Test source".into(),
episode_id: 1, episode_id: 1,
quality: quality_wrapper(), quality: quality_wrapper(),
languages: vec![language()], languages: vec![Some(language())],
date: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()), date: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()),
event_type: SonarrHistoryEventType::Grabbed, event_type: SonarrHistoryEventType::Grabbed,
data: history_data(), data: history_data(),
@@ -377,7 +377,7 @@ pub(in crate::network::sonarr_network) mod test_utils {
rejections: Some(rejections()), rejections: Some(rejections()),
seeders: Some(Number::from(2)), seeders: Some(Number::from(2)),
leechers: Some(Number::from(1)), leechers: Some(Number::from(1)),
languages: Some(vec![language()]), languages: Some(vec![Some(language())]),
quality: quality_wrapper(), quality: quality_wrapper(),
full_season: false, full_season: false,
} }
+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
@@ -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 title = series_title.as_ref().unwrap_or(&String::new()).to_owned();
let languages_string = languages let languages_string = languages
.iter() .iter()
.map(|lang| lang.name.to_owned()) .map(|lang| lang.as_ref().unwrap_or(&Default::default()).name.to_owned())
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", "); .join(", ");
+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),
); );
} }
+9 -2
View File
@@ -1,5 +1,6 @@
use crate::app::App; use crate::app::App;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, HISTORY_BLOCKS}; 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::sonarr_models::{SonarrHistoryEventType, SonarrHistoryItem};
use crate::models::Route; use crate::models::Route;
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
@@ -68,7 +69,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![
@@ -77,7 +78,13 @@ fn draw_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
Cell::from( Cell::from(
languages languages
.iter() .iter()
.map(|language| language.name.to_owned()) .map(|language| {
language
.as_ref()
.unwrap_or(&Language::default())
.name
.to_owned()
})
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(","), .join(","),
), ),
@@ -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![
+15 -4
View File
@@ -1,5 +1,6 @@
use crate::app::App; use crate::app::App;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS}; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS};
use crate::models::servarr_models::Language;
use crate::models::sonarr_models::{ use crate::models::sonarr_models::{
DownloadRecord, DownloadStatus, Episode, SonarrHistoryEventType, SonarrHistoryItem, SonarrRelease, DownloadRecord, DownloadStatus, Episode, SonarrHistoryEventType, SonarrHistoryItem, SonarrRelease,
}; };
@@ -271,7 +272,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![
@@ -280,7 +281,13 @@ fn draw_episode_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
Cell::from( Cell::from(
languages languages
.iter() .iter()
.map(|language| language.name.to_owned()) .map(|language| {
language
.as_ref()
.unwrap_or(&Language::default())
.name
.to_owned()
})
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(","), .join(","),
), ),
@@ -415,7 +422,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 { "" };
@@ -441,7 +448,11 @@ fn draw_episode_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
}; };
let language = if languages.is_some() { 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 { } else {
String::new() String::new()
}; };
+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();
+14 -4
View File
@@ -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![
@@ -276,7 +276,13 @@ fn draw_season_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
Cell::from( Cell::from(
languages languages
.iter() .iter()
.map(|language| language.name.to_owned()) .map(|language| {
language
.as_ref()
.unwrap_or(&Default::default())
.name
.to_owned()
})
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(","), .join(","),
), ),
@@ -372,7 +378,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 { "" };
@@ -398,7 +404,11 @@ fn draw_season_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
}; };
let language = if languages.is_some() { 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 { } else {
String::new() String::new()
}; };
@@ -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![
@@ -324,7 +324,13 @@ fn draw_series_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
Cell::from( Cell::from(
languages languages
.iter() .iter()
.map(|language| language.name.to_owned()) .map(|language| {
language
.as_ref()
.unwrap_or(&Default::default())
.name
.to_owned()
})
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(","), .join(","),
), ),