feat: CLI support for listing artists
This commit is contained in:
+37
-1
@@ -13,6 +13,7 @@ use tokio_util::sync::CancellationToken;
|
|||||||
use veil::Redact;
|
use veil::Redact;
|
||||||
|
|
||||||
use crate::cli::Command;
|
use crate::cli::Command;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
|
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
|
||||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData};
|
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData};
|
||||||
use crate::models::servarr_models::KeybindingItem;
|
use crate::models::servarr_models::KeybindingItem;
|
||||||
@@ -96,6 +97,26 @@ impl App<'_> {
|
|||||||
server_tabs.extend(sonarr_tabs);
|
server_tabs.extend(sonarr_tabs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(lidarr_configs) = config.lidarr {
|
||||||
|
let mut unnamed_idx = 0;
|
||||||
|
let lidarr_tabs = lidarr_configs.into_iter().map(|lidarr_config| {
|
||||||
|
let name = if let Some(name) = lidarr_config.name.clone() {
|
||||||
|
name
|
||||||
|
} else {
|
||||||
|
unnamed_idx += 1;
|
||||||
|
format!("Lidarr {unnamed_idx}")
|
||||||
|
};
|
||||||
|
|
||||||
|
TabRoute {
|
||||||
|
title: name,
|
||||||
|
route: ActiveLidarrBlock::Artists.into(),
|
||||||
|
contextual_help: None,
|
||||||
|
config: Some(lidarr_config),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server_tabs.extend(lidarr_tabs);
|
||||||
|
}
|
||||||
|
|
||||||
let weight_sorted_tabs = server_tabs
|
let weight_sorted_tabs = server_tabs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.sorted_by(|tab1, tab2| {
|
.sorted_by(|tab1, tab2| {
|
||||||
@@ -303,13 +324,14 @@ pub struct Data<'a> {
|
|||||||
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
pub theme: Option<String>,
|
pub theme: Option<String>,
|
||||||
|
pub lidarr: Option<Vec<ServarrConfig>>,
|
||||||
pub radarr: Option<Vec<ServarrConfig>>,
|
pub radarr: Option<Vec<ServarrConfig>>,
|
||||||
pub sonarr: Option<Vec<ServarrConfig>>,
|
pub sonarr: Option<Vec<ServarrConfig>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppConfig {
|
impl AppConfig {
|
||||||
pub fn validate(&self) {
|
pub fn validate(&self) {
|
||||||
if self.radarr.is_none() && self.sonarr.is_none() {
|
if self.lidarr.is_none() && self.radarr.is_none() && self.sonarr.is_none() {
|
||||||
log_and_print_error(
|
log_and_print_error(
|
||||||
"No Servarr configuration provided in the specified configuration file".to_owned(),
|
"No Servarr configuration provided in the specified configuration file".to_owned(),
|
||||||
);
|
);
|
||||||
@@ -323,6 +345,10 @@ impl AppConfig {
|
|||||||
if let Some(sonarr_configs) = &self.sonarr {
|
if let Some(sonarr_configs) = &self.sonarr {
|
||||||
sonarr_configs.iter().for_each(|config| config.validate());
|
sonarr_configs.iter().for_each(|config| config.validate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(lidarr_configs) = &self.lidarr {
|
||||||
|
lidarr_configs.iter().for_each(|config| config.validate());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_config_present_for_cli(&self, command: &Command) {
|
pub fn verify_config_present_for_cli(&self, command: &Command) {
|
||||||
@@ -340,6 +366,10 @@ impl AppConfig {
|
|||||||
msg("Sonarr");
|
msg("Sonarr");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
|
Command::Lidarr(_) if self.lidarr.is_none() => {
|
||||||
|
msg("Lidarr");
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,6 +386,12 @@ impl AppConfig {
|
|||||||
sonarr_config.post_process_initialization();
|
sonarr_config.post_process_initialization();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(lidarr_configs) = self.lidarr.as_mut() {
|
||||||
|
for lidarr_config in lidarr_configs {
|
||||||
|
lidarr_config.post_process_initialization();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Subcommand;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
cli::{CliCommandHandler, Command},
|
||||||
|
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::LidarrCommand;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
|
pub enum LidarrListCommand {
|
||||||
|
#[command(about = "List all artists in your Lidarr library")]
|
||||||
|
Artists,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LidarrListCommand> for Command {
|
||||||
|
fn from(value: LidarrListCommand) -> Self {
|
||||||
|
Command::Lidarr(LidarrCommand::List(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct LidarrListCommandHandler<'a, 'b> {
|
||||||
|
_app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrListCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrListCommand> for LidarrListCommandHandler<'a, 'b> {
|
||||||
|
fn with(
|
||||||
|
app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrListCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
) -> Self {
|
||||||
|
LidarrListCommandHandler {
|
||||||
|
_app: app,
|
||||||
|
command,
|
||||||
|
network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle(self) -> Result<String> {
|
||||||
|
let result = match self.command {
|
||||||
|
LidarrListCommand::Artists => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::ListArtists.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Subcommand;
|
||||||
|
use list_command_handler::{LidarrListCommand, LidarrListCommandHandler};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
network::NetworkTrait,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{CliCommandHandler, Command};
|
||||||
|
|
||||||
|
mod list_command_handler;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
|
pub enum LidarrCommand {
|
||||||
|
#[command(
|
||||||
|
subcommand,
|
||||||
|
about = "Commands to list attributes from your Lidarr instance"
|
||||||
|
)]
|
||||||
|
List(LidarrListCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LidarrCommand> for Command {
|
||||||
|
fn from(lidarr_command: LidarrCommand) -> Command {
|
||||||
|
Command::Lidarr(lidarr_command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct LidarrCliHandler<'a, 'b> {
|
||||||
|
app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, 'b> {
|
||||||
|
fn with(
|
||||||
|
app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
) -> Self {
|
||||||
|
LidarrCliHandler {
|
||||||
|
app,
|
||||||
|
command,
|
||||||
|
network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle(self) -> Result<String> {
|
||||||
|
let result = match self.command {
|
||||||
|
LidarrCommand::List(list_command) => {
|
||||||
|
LidarrListCommandHandler::with(self.app, list_command, self.network)
|
||||||
|
.handle()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,12 +3,14 @@ use std::sync::Arc;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Subcommand, command};
|
use clap::{Subcommand, command};
|
||||||
use clap_complete::Shell;
|
use clap_complete::Shell;
|
||||||
|
use lidarr::{LidarrCliHandler, LidarrCommand};
|
||||||
use radarr::{RadarrCliHandler, RadarrCommand};
|
use radarr::{RadarrCliHandler, RadarrCommand};
|
||||||
use sonarr::{SonarrCliHandler, SonarrCommand};
|
use sonarr::{SonarrCliHandler, SonarrCommand};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::{app::App, network::NetworkTrait};
|
use crate::{app::App, network::NetworkTrait};
|
||||||
|
|
||||||
|
pub mod lidarr;
|
||||||
pub mod radarr;
|
pub mod radarr;
|
||||||
pub mod sonarr;
|
pub mod sonarr;
|
||||||
|
|
||||||
@@ -24,6 +26,9 @@ pub enum Command {
|
|||||||
#[command(subcommand, about = "Commands for manging your Sonarr instance")]
|
#[command(subcommand, about = "Commands for manging your Sonarr instance")]
|
||||||
Sonarr(SonarrCommand),
|
Sonarr(SonarrCommand),
|
||||||
|
|
||||||
|
#[command(subcommand, about = "Commands for manging your Lidarr instance")]
|
||||||
|
Lidarr(LidarrCommand),
|
||||||
|
|
||||||
#[command(
|
#[command(
|
||||||
arg_required_else_help = true,
|
arg_required_else_help = true,
|
||||||
about = "Generate shell completions for the Managarr CLI"
|
about = "Generate shell completions for the Managarr CLI"
|
||||||
@@ -61,6 +66,11 @@ pub(crate) async fn handle_command(
|
|||||||
.handle()
|
.handle()
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
Command::Lidarr(lidarr_command) => {
|
||||||
|
LidarrCliHandler::with(app, lidarr_command, network)
|
||||||
|
.handle()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
_ => String::new(),
|
_ => String::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -145,7 +145,7 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
Some(command) => match command {
|
Some(command) => match command {
|
||||||
Command::Radarr(_) | Command::Sonarr(_) => {
|
Command::Radarr(_) | Command::Sonarr(_) | Command::Lidarr(_) => {
|
||||||
if spinner_disabled {
|
if spinner_disabled {
|
||||||
start_cli_no_spinner(config, reqwest_client, cancellation_token, app, command).await;
|
start_cli_no_spinner(config, reqwest_client, cancellation_token, app, command).await;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use derivative::Derivative;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::{Number, Value};
|
||||||
|
|
||||||
|
use super::{HorizontallyScrollableText, Serdeable};
|
||||||
|
use crate::serde_enum_from;
|
||||||
|
|
||||||
|
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Artist {
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub id: i64,
|
||||||
|
pub mb_id: String,
|
||||||
|
pub artist_name: HorizontallyScrollableText,
|
||||||
|
pub foreign_artist_id: String,
|
||||||
|
pub status: ArtistStatus,
|
||||||
|
pub overview: Option<String>,
|
||||||
|
pub artist_type: Option<String>,
|
||||||
|
pub disambiguation: Option<String>,
|
||||||
|
pub path: String,
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub quality_profile_id: i64,
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub metadata_profile_id: i64,
|
||||||
|
pub monitored: bool,
|
||||||
|
pub genres: Vec<String>,
|
||||||
|
pub tags: Vec<Number>,
|
||||||
|
pub added: DateTime<Utc>,
|
||||||
|
pub ratings: Option<Ratings>,
|
||||||
|
pub statistics: Option<ArtistStatistics>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub enum ArtistStatus {
|
||||||
|
#[default]
|
||||||
|
Continuing,
|
||||||
|
Ended,
|
||||||
|
Deleted,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Ratings {
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub votes: i64,
|
||||||
|
#[serde(deserialize_with = "super::from_f64")]
|
||||||
|
pub value: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Ratings {}
|
||||||
|
|
||||||
|
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ArtistStatistics {
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub album_count: i64,
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub track_file_count: i64,
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub track_count: i64,
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub total_track_count: i64,
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub size_on_disk: i64,
|
||||||
|
#[serde(deserialize_with = "super::from_f64")]
|
||||||
|
pub percent_of_tracks: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for ArtistStatistics {}
|
||||||
|
|
||||||
|
impl From<LidarrSerdeable> for Serdeable {
|
||||||
|
fn from(value: LidarrSerdeable) -> Serdeable {
|
||||||
|
Serdeable::Lidarr(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serde_enum_from!(
|
||||||
|
LidarrSerdeable {
|
||||||
|
Artists(Vec<Artist>),
|
||||||
|
Value(Value),
|
||||||
|
}
|
||||||
|
);
|
||||||
+8
-5
@@ -1,16 +1,19 @@
|
|||||||
use std::fmt::{Debug, Display, Formatter};
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
use crate::app::ServarrConfig;
|
|
||||||
use crate::app::context_clues::ContextClue;
|
use crate::app::context_clues::ContextClue;
|
||||||
|
use crate::app::ServarrConfig;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||||
|
use lidarr_models::LidarrSerdeable;
|
||||||
use radarr_models::RadarrSerdeable;
|
use radarr_models::RadarrSerdeable;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
|
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use serde_json::Number;
|
use serde_json::Number;
|
||||||
use servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
|
use servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
|
||||||
use sonarr_models::SonarrSerdeable;
|
use sonarr_models::SonarrSerdeable;
|
||||||
|
|
||||||
|
pub mod lidarr_models;
|
||||||
pub mod radarr_models;
|
pub mod radarr_models;
|
||||||
pub mod servarr_data;
|
pub mod servarr_data;
|
||||||
pub mod servarr_models;
|
pub mod servarr_models;
|
||||||
@@ -30,7 +33,7 @@ pub enum Route {
|
|||||||
Radarr(ActiveRadarrBlock, Option<ActiveRadarrBlock>),
|
Radarr(ActiveRadarrBlock, Option<ActiveRadarrBlock>),
|
||||||
Sonarr(ActiveSonarrBlock, Option<ActiveSonarrBlock>),
|
Sonarr(ActiveSonarrBlock, Option<ActiveSonarrBlock>),
|
||||||
Readarr,
|
Readarr,
|
||||||
Lidarr,
|
Lidarr(ActiveLidarrBlock, Option<ActiveLidarrBlock>),
|
||||||
Whisparr,
|
Whisparr,
|
||||||
Bazarr,
|
Bazarr,
|
||||||
Prowlarr,
|
Prowlarr,
|
||||||
@@ -43,6 +46,7 @@ pub enum Route {
|
|||||||
pub enum Serdeable {
|
pub enum Serdeable {
|
||||||
Radarr(RadarrSerdeable),
|
Radarr(RadarrSerdeable),
|
||||||
Sonarr(SonarrSerdeable),
|
Sonarr(SonarrSerdeable),
|
||||||
|
Lidarr(LidarrSerdeable),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Scrollable {
|
pub trait Scrollable {
|
||||||
@@ -289,8 +293,7 @@ impl TabState {
|
|||||||
TabState { tabs, index: 0 }
|
TabState { tabs, index: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allowing this code for now since we'll eventually be implementing additional Servarr support, and we'll need it then
|
#[cfg(test)]
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn set_index(&mut self, index: usize) -> &TabRoute {
|
pub fn set_index(&mut self, index: usize) -> &TabRoute {
|
||||||
self.index = index;
|
self.index = index;
|
||||||
&self.tabs[self.index]
|
&self.tabs[self.index]
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
use strum::EnumIter;
|
||||||
|
#[cfg(test)]
|
||||||
|
use strum::{Display, EnumString};
|
||||||
|
|
||||||
|
use crate::models::Route;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "lidarr_data_tests.rs"]
|
||||||
|
mod lidarr_data_tests;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, EnumIter)]
|
||||||
|
#[cfg_attr(test, derive(Display, EnumString))]
|
||||||
|
pub enum ActiveLidarrBlock {
|
||||||
|
#[default]
|
||||||
|
Artists,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ActiveLidarrBlock> for Route {
|
||||||
|
fn from(active_lidarr_block: ActiveLidarrBlock) -> Route {
|
||||||
|
Route::Lidarr(active_lidarr_block, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(ActiveLidarrBlock, Option<ActiveLidarrBlock>)> for Route {
|
||||||
|
fn from(value: (ActiveLidarrBlock, Option<ActiveLidarrBlock>)) -> Route {
|
||||||
|
Route::Lidarr(value.0, value.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use crate::models::{servarr_data::lidarr::lidarr_data::ActiveLidarrBlock, Route};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_active_lidarr_block_to_route() {
|
||||||
|
assert_eq!(
|
||||||
|
Route::from(ActiveLidarrBlock::Artists),
|
||||||
|
Route::Lidarr(ActiveLidarrBlock::Artists, None)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_tuple_to_route_with_context() {
|
||||||
|
assert_eq!(
|
||||||
|
Route::from((ActiveLidarrBlock::Artists, Some(ActiveLidarrBlock::Artists))),
|
||||||
|
Route::Lidarr(ActiveLidarrBlock::Artists, Some(ActiveLidarrBlock::Artists),)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod lidarr_data;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
|
|
||||||
|
pub mod lidarr;
|
||||||
pub mod modals;
|
pub mod modals;
|
||||||
pub mod radarr;
|
pub mod radarr;
|
||||||
pub mod sonarr;
|
pub mod sonarr;
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
use super::{Network, NetworkEvent, NetworkResource};
|
||||||
|
use crate::{
|
||||||
|
models::lidarr_models::{Artist, LidarrSerdeable},
|
||||||
|
network::RequestMethod,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
|
pub enum LidarrEvent {
|
||||||
|
HealthCheck,
|
||||||
|
ListArtists,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetworkResource for LidarrEvent {
|
||||||
|
fn resource(&self) -> &'static str {
|
||||||
|
match &self {
|
||||||
|
LidarrEvent::HealthCheck => "/health",
|
||||||
|
LidarrEvent::ListArtists => "/artist",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LidarrEvent> for NetworkEvent {
|
||||||
|
fn from(lidarr_event: LidarrEvent) -> Self {
|
||||||
|
NetworkEvent::Lidarr(lidarr_event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Network<'_, '_> {
|
||||||
|
pub async fn handle_lidarr_event(
|
||||||
|
&mut self,
|
||||||
|
lidarr_event: LidarrEvent,
|
||||||
|
) -> Result<LidarrSerdeable> {
|
||||||
|
match lidarr_event {
|
||||||
|
LidarrEvent::HealthCheck => self
|
||||||
|
.get_lidarr_healthcheck()
|
||||||
|
.await
|
||||||
|
.map(LidarrSerdeable::from),
|
||||||
|
LidarrEvent::ListArtists => self.list_artists().await.map(LidarrSerdeable::from),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_lidarr_healthcheck(&mut self) -> Result<()> {
|
||||||
|
info!("Performing Lidarr health check");
|
||||||
|
let event = LidarrEvent::HealthCheck;
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(event, RequestMethod::Get, None::<()>, None, None)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), ()>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_artists(&mut self) -> Result<Vec<Artist>> {
|
||||||
|
info!("Fetching Lidarr artists");
|
||||||
|
let event = LidarrEvent::ListArtists;
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(event, RequestMethod::Get, None::<()>, None, None)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), Vec<Artist>>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-5
@@ -8,6 +8,7 @@ use regex::Regex;
|
|||||||
use reqwest::{Client, RequestBuilder};
|
use reqwest::{Client, RequestBuilder};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
|
use lidarr_network::LidarrEvent;
|
||||||
use sonarr_network::SonarrEvent;
|
use sonarr_network::SonarrEvent;
|
||||||
use strum_macros::Display;
|
use strum_macros::Display;
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
@@ -21,6 +22,7 @@ use crate::network::radarr_network::RadarrEvent;
|
|||||||
use mockall::automock;
|
use mockall::automock;
|
||||||
use reqwest::header::HeaderMap;
|
use reqwest::header::HeaderMap;
|
||||||
|
|
||||||
|
pub mod lidarr_network;
|
||||||
pub mod radarr_network;
|
pub mod radarr_network;
|
||||||
pub mod sonarr_network;
|
pub mod sonarr_network;
|
||||||
mod utils;
|
mod utils;
|
||||||
@@ -44,6 +46,7 @@ pub trait NetworkResource {
|
|||||||
pub enum NetworkEvent {
|
pub enum NetworkEvent {
|
||||||
Radarr(RadarrEvent),
|
Radarr(RadarrEvent),
|
||||||
Sonarr(SonarrEvent),
|
Sonarr(SonarrEvent),
|
||||||
|
Lidarr(LidarrEvent),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -65,6 +68,10 @@ impl NetworkTrait for Network<'_, '_> {
|
|||||||
.handle_sonarr_event(sonarr_event)
|
.handle_sonarr_event(sonarr_event)
|
||||||
.await
|
.await
|
||||||
.map(Serdeable::from),
|
.map(Serdeable::from),
|
||||||
|
NetworkEvent::Lidarr(lidarr_event) => self
|
||||||
|
.handle_lidarr_event(lidarr_event)
|
||||||
|
.await
|
||||||
|
.map(Serdeable::from),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut app = self.app.lock().await;
|
let mut app = self.app.lock().await;
|
||||||
@@ -229,12 +236,14 @@ impl<'a, 'b> Network<'a, 'b> {
|
|||||||
.get_active_config()
|
.get_active_config()
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("Servarr config is undefined");
|
.expect("Servarr config is undefined");
|
||||||
let default_port = match network_event.into() {
|
let network_event_type = network_event.into();
|
||||||
NetworkEvent::Radarr(_) => 7878,
|
let (default_port, api_version) = match &network_event_type {
|
||||||
NetworkEvent::Sonarr(_) => 8989,
|
NetworkEvent::Radarr(_) => (7878, "v3"),
|
||||||
|
NetworkEvent::Sonarr(_) => (8989, "v3"),
|
||||||
|
NetworkEvent::Lidarr(_) => (8686, "v1"),
|
||||||
};
|
};
|
||||||
let mut uri = if let Some(servarr_uri) = uri {
|
let mut uri = if let Some(servarr_uri) = uri {
|
||||||
format!("{servarr_uri}/api/v3{resource}")
|
format!("{servarr_uri}/api/{api_version}{resource}")
|
||||||
} else {
|
} else {
|
||||||
let protocol = if ssl_cert_path.is_some() {
|
let protocol = if ssl_cert_path.is_some() {
|
||||||
"https"
|
"https"
|
||||||
@@ -243,7 +252,7 @@ impl<'a, 'b> Network<'a, 'b> {
|
|||||||
};
|
};
|
||||||
let host = host.as_ref().unwrap();
|
let host = host.as_ref().unwrap();
|
||||||
format!(
|
format!(
|
||||||
"{protocol}://{host}:{}/api/v3{resource}",
|
"{protocol}://{host}:{}/api/{api_version}{resource}",
|
||||||
port.unwrap_or(default_port)
|
port.unwrap_or(default_port)
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|||||||
+8
-3
@@ -6,10 +6,10 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use anyhow::{Context, anyhow};
|
use anyhow::{anyhow, Context};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use log::{LevelFilter, error};
|
use log::{error, LevelFilter};
|
||||||
use log4rs::append::file::FileAppender;
|
use log4rs::append::file::FileAppender;
|
||||||
use log4rs::config::{Appender, Root};
|
use log4rs::config::{Appender, Root};
|
||||||
use log4rs::encode::pattern::PatternEncoder;
|
use log4rs::encode::pattern::PatternEncoder;
|
||||||
@@ -18,7 +18,7 @@ use reqwest::{Certificate, Client};
|
|||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use crate::app::{App, AppConfig, log_and_print_error};
|
use crate::app::{log_and_print_error, App, AppConfig};
|
||||||
use crate::cli::{self, Command};
|
use crate::cli::{self, Command};
|
||||||
use crate::network::Network;
|
use crate::network::Network;
|
||||||
use crate::ui::theme::ThemeDefinitionsWrapper;
|
use crate::ui::theme::ThemeDefinitionsWrapper;
|
||||||
@@ -318,6 +318,11 @@ pub fn select_cli_configuration(
|
|||||||
config.sonarr.as_ref().expect("Sonarr config must exist")[0].clone();
|
config.sonarr.as_ref().expect("Sonarr config must exist")[0].clone();
|
||||||
app.server_tabs.select_tab_by_config(&default_sonarr_config);
|
app.server_tabs.select_tab_by_config(&default_sonarr_config);
|
||||||
}
|
}
|
||||||
|
Command::Lidarr(_) => {
|
||||||
|
let default_lidarr_config =
|
||||||
|
config.lidarr.as_ref().expect("Lidarr config must exist")[0].clone();
|
||||||
|
app.server_tabs.select_tab_by_config(&default_lidarr_config);
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user