feat(network): Added get quality profiles and get episode details events for Sonarr

This commit is contained in:
2024-11-15 18:19:03 -07:00
parent 1fe95d057b
commit e14b7072c6
11 changed files with 941 additions and 27 deletions
+205 -5
View File
@@ -1,19 +1,22 @@
use std::collections::BTreeMap;
use anyhow::Result;
use indoc::formatdoc;
use log::info;
use managarr_tree_widget::TreeItem;
use serde_json::{json, Value};
use crate::{
models::{
servarr_data::sonarr::sonarr_data::ActiveSonarrBlock,
servarr_data::sonarr::{modals::EpisodeDetailsModal, sonarr_data::ActiveSonarrBlock},
sonarr_models::{
BlocklistResponse, Episode, LogResponse, Series, SonarrSerdeable, SystemStatus,
BlocklistResponse, DownloadRecord, Episode, LogResponse, QualityProfile, Series,
SonarrSerdeable, SystemStatus,
},
HorizontallyScrollableText, Route, Scrollable,
HorizontallyScrollableText, Route, Scrollable, ScrollableText,
},
network::RequestMethod,
utils::convert_to_gb,
};
use super::{Network, NetworkEvent, NetworkResource};
@@ -26,8 +29,10 @@ pub enum SonarrEvent {
ClearBlocklist,
DeleteBlocklistItem(Option<i64>),
GetBlocklist,
GetEpisodeDetails(Option<i64>),
GetEpisodes(Option<i64>),
GetLogs(Option<u64>),
GetQualityProfiles,
GetStatus,
HealthCheck,
ListSeries,
@@ -39,8 +44,9 @@ impl NetworkResource for SonarrEvent {
SonarrEvent::ClearBlocklist => "/blocklist/bulk",
SonarrEvent::DeleteBlocklistItem(_) => "/blocklist",
SonarrEvent::GetBlocklist => "/blocklist?page=1&pageSize=10000",
SonarrEvent::GetEpisodes(_) => "/episode",
SonarrEvent::GetEpisodes(_) | SonarrEvent::GetEpisodeDetails(_) => "/episode",
SonarrEvent::GetLogs(_) => "/log",
SonarrEvent::GetQualityProfiles => "/qualityprofile",
SonarrEvent::GetStatus => "/system/status",
SonarrEvent::HealthCheck => "/health",
SonarrEvent::ListSeries => "/series",
@@ -73,6 +79,14 @@ impl<'a, 'b> Network<'a, 'b> {
.get_episodes(series_id)
.await
.map(SonarrSerdeable::from),
SonarrEvent::GetEpisodeDetails(episode_id) => self
.get_episode_details(episode_id)
.await
.map(SonarrSerdeable::from),
SonarrEvent::GetQualityProfiles => self
.get_sonarr_quality_profiles()
.await
.map(SonarrSerdeable::from),
SonarrEvent::GetLogs(events) => self
.get_sonarr_logs(events)
.await
@@ -204,6 +218,22 @@ impl<'a, 'b> Network<'a, 'b> {
self
.handle_request::<(), Vec<Episode>>(request_props, |mut episode_vec, mut app| {
episode_vec.sort_by(|a, b| a.id.cmp(&b.id));
if !matches!(
app.get_current_route(),
Route::Sonarr(ActiveSonarrBlock::EpisodesTableSortPrompt, _)
) {
app
.data
.sonarr_data
.episodes_table
.set_items(episode_vec.clone());
app
.data
.sonarr_data
.episodes_table
.apply_sorting_toggle(false);
}
let mut seasons = BTreeMap::new();
for episode in episode_vec {
@@ -226,7 +256,114 @@ impl<'a, 'b> Network<'a, 'b> {
})
.collect();
app.data.sonarr_data.episodes.set_items(tree);
app.data.sonarr_data.episodes_tree.set_items(tree);
})
.await
}
async fn get_episode_details(&mut self, episode_id: Option<i64>) -> Result<Episode> {
info!("Fetching Sonarr episode details");
let event = SonarrEvent::GetEpisodeDetails(None);
let id = self.extract_episode_id(episode_id).await;
info!("Fetching episode details for episode with ID: {id}");
let request_props = self
.request_props_from(
event,
RequestMethod::Get,
None::<()>,
Some(format!("/{id}")),
None,
)
.await;
self
.handle_request::<(), Episode>(request_props, |episode_response, mut app| {
let Episode {
id,
title,
air_date_utc,
overview,
has_file,
season_number,
episode_number,
episode_file,
..
} = episode_response;
let status = get_episode_status(has_file, &app.data.sonarr_data.downloads.items, id);
let air_date = if let Some(air_date) = air_date_utc {
format!("{air_date}")
} else {
String::new()
};
let mut episode_details_modal = EpisodeDetailsModal {
episode_details: ScrollableText::with_string(formatdoc!(
"
Title: {}
Season: {season_number}
Episode Number: {episode_number}
Air Date: {air_date}
Status: {status}
Description: {}",
title.unwrap_or_default(),
overview.unwrap_or_default(),
)),
..EpisodeDetailsModal::default()
};
if let Some(file) = episode_file {
let size = convert_to_gb(file.size);
episode_details_modal.file_details = formatdoc!(
"
Relative Path: {}
Absolute Path: {}
Size: {size:.2} GB
Language: {}
Date Added: {}",
file.relative_path,
file.path,
file.language.name,
file.date_added,
);
if let Some(media_info) = file.media_info {
episode_details_modal.audio_details = formatdoc!(
"
Bitrate: {}
Channels: {:.1}
Codec: {}
Languages: {}
Stream Count: {}",
media_info.audio_bitrate,
media_info.audio_channels.as_f64().unwrap(),
media_info.audio_codec.unwrap_or_default(),
media_info.audio_languages.unwrap_or_default(),
media_info.audio_stream_count
);
episode_details_modal.video_details = formatdoc!(
"
Bit Depth: {}
Bitrate: {}
Codec: {}
FPS: {}
Resolution: {}
Scan Type: {}
Runtime: {}
Subtitles: {}",
media_info.video_bit_depth,
media_info.video_bitrate,
media_info.video_codec,
media_info.video_fps.as_f64().unwrap(),
media_info.resolution,
media_info.scan_type,
media_info.run_time,
media_info.subtitles.unwrap_or_default()
);
}
};
app.data.sonarr_data.episode_details_modal = Some(episode_details_modal);
})
.await
}
@@ -278,6 +415,24 @@ impl<'a, 'b> Network<'a, 'b> {
.await
}
async fn get_sonarr_quality_profiles(&mut self) -> Result<Vec<QualityProfile>> {
info!("Fetching Sonarr quality profiles");
let event = SonarrEvent::GetQualityProfiles;
let request_props = self
.request_props_from(event, RequestMethod::Get, None::<()>, None, None)
.await;
self
.handle_request::<(), Vec<QualityProfile>>(request_props, |quality_profiles, mut app| {
app.data.sonarr_data.quality_profile_map = quality_profiles
.into_iter()
.map(|profile| (profile.id, profile.name))
.collect();
})
.await
}
async fn list_series(&mut self) -> Result<Vec<Series>> {
info!("Fetching Sonarr library");
let event = SonarrEvent::ListSeries;
@@ -332,4 +487,49 @@ impl<'a, 'b> Network<'a, 'b> {
};
(series_id, format!("seriesId={series_id}"))
}
async fn extract_episode_id(&mut self, episode_id: Option<i64>) -> i64 {
let app = self.app.lock().await;
let episode_id = if let Some(id) = episode_id {
id
} else if matches!(
app.get_current_route(),
Route::Sonarr(ActiveSonarrBlock::EpisodesTable, _)
) {
app.data.sonarr_data.episodes_table.current_selection().id
} else {
app
.data
.sonarr_data
.episodes_tree
.current_selection()
.as_ref()
.unwrap()
.id
};
episode_id
}
}
fn get_episode_status(has_file: bool, downloads_vec: &[DownloadRecord], episode_id: i64) -> String {
if !has_file {
if let Some(download) = downloads_vec
.iter()
.find(|&download| download.episode_id == episode_id)
{
if download.status == "downloading" {
return "Downloading".to_owned();
}
if download.status == "completed" {
return "Awaiting Import".to_owned();
}
}
return "Missing".to_owned();
}
"Downloaded".to_owned()
}