use std::fmt::{Display, Formatter}; use chrono::{DateTime, Utc}; use clap::ValueEnum; use derivative::Derivative; use serde::{Deserialize, Serialize}; use serde_json::{json, Number, Value}; use strum::EnumIter; use crate::serde_enum_from; use super::{ radarr_models::IndexerTestResult, servarr_models::{ DiskSpace, HostConfig, Indexer, Language, LogResponse, QualityProfile, QualityWrapper, QueueEvent, Release, RootFolder, SecurityConfig, Tag, Update, }, EnumDisplayStyle, HorizontallyScrollableText, Serdeable, }; #[cfg(test)] #[path = "sonarr_models_tests.rs"] mod sonarr_models_tests; #[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct BlocklistItem { #[serde(deserialize_with = "super::from_i64")] pub id: i64, #[serde(deserialize_with = "super::from_i64")] pub series_id: i64, pub episode_ids: Vec, pub source_title: String, pub language: Language, pub quality: QualityWrapper, pub date: DateTime, pub protocol: String, pub indexer: String, pub message: String, } #[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct BlocklistResponse { pub records: Vec, } #[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)] #[serde(rename_all = "camelCase")] pub struct DownloadRecord { pub title: String, pub status: String, #[serde(deserialize_with = "super::from_i64")] pub id: i64, #[serde(deserialize_with = "super::from_i64")] pub episode_id: i64, #[serde(deserialize_with = "super::from_f64")] pub size: f64, #[serde(deserialize_with = "super::from_f64")] pub sizeleft: f64, pub output_path: Option, #[serde(default)] pub indexer: String, pub download_client: String, } impl Eq for DownloadRecord {} #[derive(Default, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct DownloadsResponse { pub records: Vec, } #[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Episode { #[serde(deserialize_with = "super::from_i64")] pub id: i64, #[serde(deserialize_with = "super::from_i64")] pub series_id: i64, #[serde(deserialize_with = "super::from_i64")] pub tvdb_id: i64, #[serde(deserialize_with = "super::from_i64")] pub episode_file_id: i64, #[serde(deserialize_with = "super::from_i64")] pub season_number: i64, #[serde(deserialize_with = "super::from_i64")] pub episode_number: i64, pub title: Option, pub air_date_utc: Option>, pub overview: Option, pub has_file: bool, pub monitored: bool, pub episode_file: Option, } impl Display for Episode { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.title.as_ref().unwrap_or(&String::new())) } } #[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct EpisodeFile { pub relative_path: String, pub path: String, #[serde(deserialize_with = "super::from_i64")] pub size: i64, pub language: Language, pub date_added: DateTime, pub media_info: Option, } #[derive(Default, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct IndexerSettings { #[serde(deserialize_with = "super::from_i64")] pub id: i64, #[serde(deserialize_with = "super::from_i64")] pub minimum_age: i64, #[serde(deserialize_with = "super::from_i64")] pub retention: i64, #[serde(deserialize_with = "super::from_i64")] pub maximum_size: i64, #[serde(deserialize_with = "super::from_i64")] pub rss_sync_interval: i64, } #[derive(Serialize, Deserialize, Derivative, Debug, Clone, PartialEq, Eq)] #[derivative(Default)] #[serde(rename_all = "camelCase")] pub struct MediaInfo { #[serde(deserialize_with = "super::from_i64")] pub audio_bitrate: i64, #[derivative(Default(value = "Number::from(0)"))] pub audio_channels: Number, pub audio_codec: Option, pub audio_languages: Option, #[serde(deserialize_with = "super::from_i64")] pub audio_stream_count: i64, #[serde(deserialize_with = "super::from_i64")] pub video_bit_depth: i64, #[serde(deserialize_with = "super::from_i64")] pub video_bitrate: i64, pub video_codec: String, #[derivative(Default(value = "Number::from(0)"))] pub video_fps: Number, pub resolution: String, pub run_time: String, pub scan_type: String, pub subtitles: Option, } #[derive(Derivative, Serialize, Deserialize, Debug, Clone, PartialEq)] #[derivative(Default)] pub struct Rating { #[serde(deserialize_with = "super::from_i64")] pub votes: i64, #[serde(deserialize_with = "super::from_f64")] pub value: f64, } impl Eq for Rating {} #[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Season { #[serde(deserialize_with = "super::from_i64")] pub season_number: i64, pub monitored: bool, pub statistics: SeasonStatistics, } #[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)] #[serde(rename_all = "camelCase")] pub struct SeasonStatistics { pub next_airing: Option>, pub previous_airing: Option>, #[serde(deserialize_with = "super::from_i64")] pub episode_file_count: i64, #[serde(deserialize_with = "super::from_i64")] pub episode_count: i64, #[serde(deserialize_with = "super::from_i64")] pub total_episode_count: i64, #[serde(deserialize_with = "super::from_i64")] pub size_on_disk: i64, #[serde(deserialize_with = "super::from_f64")] pub percent_of_episodes: f64, } impl Eq for SeasonStatistics {} #[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Series { #[serde(deserialize_with = "super::from_i64")] pub id: i64, #[serde(deserialize_with = "super::from_i64")] pub tvdb_id: i64, pub title: HorizontallyScrollableText, #[serde(deserialize_with = "super::from_i64")] pub quality_profile_id: i64, #[serde(deserialize_with = "super::from_i64")] pub language_profile_id: i64, #[serde(deserialize_with = "super::from_i64")] pub runtime: i64, #[serde(deserialize_with = "super::from_i64")] pub year: i64, pub monitored: bool, pub series_type: SeriesType, pub path: String, pub genres: Vec, pub tags: Vec, pub ratings: Rating, pub ended: bool, pub status: SeriesStatus, pub overview: String, pub network: Option, pub season_folder: bool, pub certification: Option, pub statistics: Option, pub seasons: Option>, } #[derive( Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter, ValueEnum, )] #[serde(rename_all = "camelCase")] pub enum SeriesType { #[default] Standard, Daily, Anime, } impl Display for SeriesType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let series_type = match self { SeriesType::Standard => "standard", SeriesType::Daily => "daily", SeriesType::Anime => "anime", }; write!(f, "{series_type}") } } impl<'a> EnumDisplayStyle<'a> for SeriesType { fn to_display_str(self) -> &'a str { match self { SeriesType::Standard => "Standard", SeriesType::Daily => "Daily", SeriesType::Anime => "Anime", } } } #[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)] #[serde(rename_all = "camelCase")] pub struct SeriesStatistics { #[serde(deserialize_with = "super::from_i64")] pub season_count: i64, #[serde(deserialize_with = "super::from_i64")] pub episode_file_count: i64, #[serde(deserialize_with = "super::from_i64")] pub episode_count: i64, #[serde(deserialize_with = "super::from_i64")] pub total_episode_count: i64, #[serde(deserialize_with = "super::from_i64")] pub size_on_disk: i64, #[serde(deserialize_with = "super::from_f64")] pub percent_of_episodes: f64, } impl Eq for SeriesStatistics {} #[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter)] #[serde(rename_all = "camelCase")] pub enum SeriesStatus { #[default] Continuing, Ended, Upcoming, Deleted, } impl Display for SeriesStatus { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let series_status = match self { SeriesStatus::Continuing => "continuing", SeriesStatus::Ended => "ended", SeriesStatus::Upcoming => "upcoming", SeriesStatus::Deleted => "deleted", }; write!(f, "{series_status}") } } impl<'a> EnumDisplayStyle<'a> for SeriesStatus { fn to_display_str(self) -> &'a str { match self { SeriesStatus::Continuing => "Continuing", SeriesStatus::Ended => "Ended", SeriesStatus::Upcoming => "Upcoming", SeriesStatus::Deleted => "Deleted", } } } #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct SonarrHistoryWrapper { pub records: Vec, } #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct SonarrHistoryData { pub dropped_path: Option, pub imported_path: Option, pub indexer: Option, pub release_group: Option, pub series_match_type: Option, pub nzb_info_url: Option, pub download_client_name: Option, pub age: Option, pub published_date: Option>, pub message: Option, pub reason: Option, } #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub enum SonarrHistoryEventType { #[default] Unknown, Grabbed, SeriesFolderImported, DownloadFolderImported, DownloadFailed, EpisodeFileDeleted, EpisodeFileRenamed, DownloadIgnored, } impl Display for SonarrHistoryEventType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let event_type = match self { SonarrHistoryEventType::Unknown => "unknown", SonarrHistoryEventType::Grabbed => "grabbed", SonarrHistoryEventType::SeriesFolderImported => "seriesFolderImported", SonarrHistoryEventType::DownloadFolderImported => "downloadFolderImported", SonarrHistoryEventType::DownloadFailed => "downloadFailed", SonarrHistoryEventType::EpisodeFileDeleted => "episodeFileDeleted", SonarrHistoryEventType::EpisodeFileRenamed => "episodeFileRenamed", SonarrHistoryEventType::DownloadIgnored => "downloadIgnored", }; write!(f, "{event_type}") } } impl<'a> EnumDisplayStyle<'a> for SonarrHistoryEventType { fn to_display_str(self) -> &'a str { match self { SonarrHistoryEventType::Unknown => "Unknown", SonarrHistoryEventType::Grabbed => "Grabbed", SonarrHistoryEventType::SeriesFolderImported => "Series Folder Imported", SonarrHistoryEventType::DownloadFolderImported => "Download Folder Imported", SonarrHistoryEventType::DownloadFailed => "Download Failed", SonarrHistoryEventType::EpisodeFileDeleted => "Episode File Deleted", SonarrHistoryEventType::EpisodeFileRenamed => "Episode File Renamed", SonarrHistoryEventType::DownloadIgnored => "Download Ignored", } } } #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct SonarrHistoryItem { #[serde(deserialize_with = "super::from_i64")] pub id: i64, pub source_title: HorizontallyScrollableText, #[serde(deserialize_with = "super::from_i64")] pub episode_id: i64, pub quality: QualityWrapper, pub language: Language, pub date: DateTime, pub event_type: String, pub data: SonarrHistoryData, } #[derive(Default, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct SonarrCommandBody { pub name: String, #[serde(skip_serializing_if = "Option::is_none")] pub series_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub season_number: Option, #[serde(skip_serializing_if = "Option::is_none")] pub episode_ids: Option>, } #[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct SonarrTask { pub name: String, pub task_name: SonarrTaskName, #[serde(deserialize_with = "super::from_i64")] pub interval: i64, pub last_execution: DateTime, pub next_execution: DateTime, } #[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Copy, ValueEnum)] #[serde(rename_all = "PascalCase")] pub enum SonarrTaskName { #[default] ApplicationUpdateCheck, Backup, CheckHealth, CleanUpRecycleBin, Housekeeping, ImportListSync, MessagingCleanup, RefreshMonitoredDownloads, RefreshSeries, RssSync, UpdateSceneMapping, } impl Display for SonarrTaskName { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let task_name = serde_json::to_string(&self) .expect("Unable to serialize task name") .replace('"', ""); write!(f, "{task_name}") } } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] #[serde(untagged)] #[allow(clippy::large_enum_variant)] pub enum SonarrSerdeable { BlocklistResponse(BlocklistResponse), DownloadsResponse(DownloadsResponse), DiskSpaces(Vec), Episode(Episode), Episodes(Vec), HostConfig(HostConfig), IndexerSettings(IndexerSettings), Indexers(Vec), IndexerTestResults(Vec), LogResponse(LogResponse), QualityProfiles(Vec), QueueEvents(Vec), Releases(Vec), RootFolders(Vec), SecurityConfig(SecurityConfig), SeriesVec(Vec), Series(Series), SonarrHistoryItems(Vec), SonarrHistoryWrapper(SonarrHistoryWrapper), SystemStatus(SystemStatus), Tag(Tag), Tags(Vec), Tasks(Vec), Updates(Vec), Value(Value), } impl From for Serdeable { fn from(value: SonarrSerdeable) -> Serdeable { Serdeable::Sonarr(value) } } impl From<()> for SonarrSerdeable { fn from(_: ()) -> Self { SonarrSerdeable::Value(json!({})) } } serde_enum_from!( SonarrSerdeable { BlocklistResponse(BlocklistResponse), DownloadsResponse(DownloadsResponse), DiskSpaces(Vec), Episode(Episode), Episodes(Vec), HostConfig(HostConfig), IndexerSettings(IndexerSettings), Indexers(Vec), IndexerTestResults(Vec), LogResponse(LogResponse), QualityProfiles(Vec), QueueEvents(Vec), Releases(Vec), RootFolders(Vec), SecurityConfig(SecurityConfig), SeriesVec(Vec), Series(Series), SonarrHistoryItems(Vec), SonarrHistoryWrapper(SonarrHistoryWrapper), SystemStatus(SystemStatus), Tag(Tag), Tags(Vec), Tasks(Vec), Updates(Vec), Value(Value), } ); #[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct SystemStatus { pub version: String, pub start_time: DateTime, }