feat(cli): Added a spinner to the CLI for long running commands like fetching releases
This commit is contained in:
Generated
+55
@@ -370,6 +370,19 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console"
|
||||||
|
version = "0.15.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
|
||||||
|
dependencies = [
|
||||||
|
"encode_unicode",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"unicode-width 0.1.14",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@@ -565,6 +578,12 @@ version = "1.13.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encode_unicode"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.35"
|
version = "0.8.35"
|
||||||
@@ -1117,6 +1136,19 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indicatif"
|
||||||
|
version = "0.17.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"number_prefix",
|
||||||
|
"portable-atomic",
|
||||||
|
"unicode-width 0.2.0",
|
||||||
|
"web-time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indoc"
|
name = "indoc"
|
||||||
version = "2.0.5"
|
version = "2.0.5"
|
||||||
@@ -1289,6 +1321,7 @@ dependencies = [
|
|||||||
"derivative",
|
"derivative",
|
||||||
"dirs-next",
|
"dirs-next",
|
||||||
"human-panic",
|
"human-panic",
|
||||||
|
"indicatif",
|
||||||
"indoc",
|
"indoc",
|
||||||
"itertools",
|
"itertools",
|
||||||
"log",
|
"log",
|
||||||
@@ -1458,6 +1491,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "number_prefix"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.5"
|
version = "0.36.5"
|
||||||
@@ -1596,6 +1635,12 @@ version = "0.3.31"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "powerfmt"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -2675,6 +2720,16 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-time"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ colored = "2.1.0"
|
|||||||
async-trait = "0.1.83"
|
async-trait = "0.1.83"
|
||||||
dirs-next = "2.0.0"
|
dirs-next = "2.0.0"
|
||||||
managarr-tree-widget = "0.24.0"
|
managarr-tree-widget = "0.24.0"
|
||||||
|
indicatif = "0.17.9"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_cmd = "2.0.16"
|
assert_cmd = "2.0.16"
|
||||||
|
|||||||
@@ -236,9 +236,11 @@ tautulli:
|
|||||||
## Environment Variables
|
## Environment Variables
|
||||||
Managarr supports using environment variables on startup so you don't have to always specify certain flags:
|
Managarr supports using environment variables on startup so you don't have to always specify certain flags:
|
||||||
|
|
||||||
| Variable | Description | Equivalent Flag |
|
| Variable | Description | Equivalent Flag |
|
||||||
| --------------------------------------- | -------------------------------- | -------------------------------- |
|
|-----------------------------------------|--------------------------------------------------------------------------------|----------------------------------|
|
||||||
| `MANAGARR_CONFIG_FILE` | Set the path to the config file | `--config` |
|
| `MANAGARR_CONFIG_FILE` | Set the path to the config file | `--config` |
|
||||||
|
| `MANAGARR_DISABLE_SPINNER` | Disable the CLI spinner (this can be useful when scripting and parsing output) | `--disable-spinner` |
|
||||||
|
|-----------------------------------------|--------------------------------------------------------------------------------|----------------------------------|
|
||||||
|
|
||||||
## Track My Progress for the Beta release (With Sonarr Support!)
|
## Track My Progress for the Beta release (With Sonarr Support!)
|
||||||
Progress for the beta release can be followed on my [Wekan Board](https://wekan.alexjclarke.com/b/dHoGjBb44MHM9HSv4/managarr)
|
Progress for the beta release can be followed on my [Wekan Board](https://wekan.alexjclarke.com/b/dHoGjBb44MHM9HSv4/managarr)
|
||||||
|
|||||||
+6
-19
@@ -42,15 +42,15 @@ pub enum Command {
|
|||||||
|
|
||||||
pub trait CliCommandHandler<'a, 'b, T: Into<Command>> {
|
pub trait CliCommandHandler<'a, 'b, T: Into<Command>> {
|
||||||
fn with(app: &'a Arc<Mutex<App<'b>>>, command: T, network: &'a mut dyn NetworkTrait) -> Self;
|
fn with(app: &'a Arc<Mutex<App<'b>>>, command: T, network: &'a mut dyn NetworkTrait) -> Self;
|
||||||
async fn handle(self) -> Result<()>;
|
async fn handle(self) -> Result<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn handle_command(
|
pub(crate) async fn handle_command(
|
||||||
app: &Arc<Mutex<App<'_>>>,
|
app: &Arc<Mutex<App<'_>>>,
|
||||||
command: Command,
|
command: Command,
|
||||||
network: &mut dyn NetworkTrait,
|
network: &mut dyn NetworkTrait,
|
||||||
) -> Result<()> {
|
) -> Result<String> {
|
||||||
match command {
|
let result = match command {
|
||||||
Command::Radarr(radarr_command) => {
|
Command::Radarr(radarr_command) => {
|
||||||
RadarrCliHandler::with(app, radarr_command, network)
|
RadarrCliHandler::with(app, radarr_command, network)
|
||||||
.handle()
|
.handle()
|
||||||
@@ -61,10 +61,10 @@ pub(crate) async fn handle_command(
|
|||||||
.handle()
|
.handle()
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => String::new(),
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -88,16 +88,3 @@ pub fn mutex_flags_or_default(positive: bool, negative: bool, default_value: boo
|
|||||||
default_value
|
default_value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! execute_network_event {
|
|
||||||
($self:ident, $event:expr) => {
|
|
||||||
let resp = $self.network.handle_network_event($event.into()).await?;
|
|
||||||
let json = serde_json::to_string_pretty(&resp)?;
|
|
||||||
println!("{}", json);
|
|
||||||
};
|
|
||||||
($self:ident, $event:expr, $happy_output:expr) => {
|
|
||||||
$self.network.handle_network_event($event.into()).await?;
|
|
||||||
println!("{}", $happy_output);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use tokio::sync::Mutex;
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
execute_network_event,
|
|
||||||
models::radarr_models::{AddMovieBody, AddOptions, MinimumAvailability, Monitor},
|
models::radarr_models::{AddMovieBody, AddOptions, MinimumAvailability, Monitor},
|
||||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
network::{radarr_network::RadarrEvent, NetworkTrait},
|
||||||
};
|
};
|
||||||
@@ -106,8 +105,8 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrAddCommand> for RadarrAddCommandHan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle(self) -> Result<()> {
|
async fn handle(self) -> Result<String> {
|
||||||
match self.command {
|
let result = match self.command {
|
||||||
RadarrAddCommand::Movie {
|
RadarrAddCommand::Movie {
|
||||||
tmdb_id,
|
tmdb_id,
|
||||||
root_folder_path,
|
root_folder_path,
|
||||||
@@ -131,19 +130,28 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrAddCommand> for RadarrAddCommandHan
|
|||||||
search_for_movie: !no_search_for_movie,
|
search_for_movie: !no_search_for_movie,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
execute_network_event!(self, RadarrEvent::AddMovie(Some(body)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::AddMovie(Some(body))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrAddCommand::RootFolder { root_folder_path } => {
|
RadarrAddCommand::RootFolder { root_folder_path } => {
|
||||||
execute_network_event!(
|
let resp = self
|
||||||
self,
|
.network
|
||||||
RadarrEvent::AddRootFolder(Some(root_folder_path.clone()))
|
.handle_network_event((RadarrEvent::AddRootFolder(Some(root_folder_path.clone()))).into())
|
||||||
);
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrAddCommand::Tag { name } => {
|
RadarrAddCommand::Tag { name } => {
|
||||||
execute_network_event!(self, RadarrEvent::AddTag(name.clone()));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::AddTag(name.clone())).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use tokio::sync::Mutex;
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
execute_network_event,
|
|
||||||
models::radarr_models::DeleteMovieParams,
|
models::radarr_models::DeleteMovieParams,
|
||||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
network::{radarr_network::RadarrEvent, NetworkTrait},
|
||||||
};
|
};
|
||||||
@@ -85,19 +84,28 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrDeleteCommand> for RadarrDeleteComm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle(self) -> Result<()> {
|
async fn handle(self) -> Result<String> {
|
||||||
match self.command {
|
let result = match self.command {
|
||||||
RadarrDeleteCommand::BlocklistItem { blocklist_item_id } => {
|
RadarrDeleteCommand::BlocklistItem { blocklist_item_id } => {
|
||||||
execute_network_event!(
|
let resp = self
|
||||||
self,
|
.network
|
||||||
RadarrEvent::DeleteBlocklistItem(Some(blocklist_item_id))
|
.handle_network_event((RadarrEvent::DeleteBlocklistItem(Some(blocklist_item_id))).into())
|
||||||
);
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrDeleteCommand::Download { download_id } => {
|
RadarrDeleteCommand::Download { download_id } => {
|
||||||
execute_network_event!(self, RadarrEvent::DeleteDownload(Some(download_id)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::DeleteDownload(Some(download_id))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrDeleteCommand::Indexer { indexer_id } => {
|
RadarrDeleteCommand::Indexer { indexer_id } => {
|
||||||
execute_network_event!(self, RadarrEvent::DeleteIndexer(Some(indexer_id)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::DeleteIndexer(Some(indexer_id))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrDeleteCommand::Movie {
|
RadarrDeleteCommand::Movie {
|
||||||
movie_id,
|
movie_id,
|
||||||
@@ -109,16 +117,28 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrDeleteCommand> for RadarrDeleteComm
|
|||||||
delete_movie_files: delete_files_from_disk,
|
delete_movie_files: delete_files_from_disk,
|
||||||
add_list_exclusion,
|
add_list_exclusion,
|
||||||
};
|
};
|
||||||
execute_network_event!(self, RadarrEvent::DeleteMovie(Some(delete_movie_params)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::DeleteMovie(Some(delete_movie_params))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrDeleteCommand::RootFolder { root_folder_id } => {
|
RadarrDeleteCommand::RootFolder { root_folder_id } => {
|
||||||
execute_network_event!(self, RadarrEvent::DeleteRootFolder(Some(root_folder_id)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::DeleteRootFolder(Some(root_folder_id))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrDeleteCommand::Tag { tag_id } => {
|
RadarrDeleteCommand::Tag { tag_id } => {
|
||||||
execute_network_event!(self, RadarrEvent::DeleteTag(tag_id));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::DeleteTag(tag_id)).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use tokio::sync::Mutex;
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{mutex_flags_or_default, mutex_flags_or_option, CliCommandHandler, Command},
|
cli::{mutex_flags_or_default, mutex_flags_or_option, CliCommandHandler, Command},
|
||||||
execute_network_event,
|
|
||||||
models::{
|
models::{
|
||||||
radarr_models::{
|
radarr_models::{
|
||||||
EditCollectionParams, EditIndexerParams, EditMovieParams, IndexerSettings,
|
EditCollectionParams, EditIndexerParams, EditMovieParams, IndexerSettings,
|
||||||
@@ -339,8 +338,8 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle(self) -> Result<()> {
|
async fn handle(self) -> Result<String> {
|
||||||
match self.command {
|
let result = match self.command {
|
||||||
RadarrEditCommand::AllIndexerSettings {
|
RadarrEditCommand::AllIndexerSettings {
|
||||||
allow_hardcoded_subs,
|
allow_hardcoded_subs,
|
||||||
disable_allow_hardcoded_subs,
|
disable_allow_hardcoded_subs,
|
||||||
@@ -389,11 +388,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH
|
|||||||
})
|
})
|
||||||
.into(),
|
.into(),
|
||||||
};
|
};
|
||||||
execute_network_event!(
|
self
|
||||||
self,
|
.network
|
||||||
RadarrEvent::EditAllIndexerSettings(Some(params)),
|
.handle_network_event((RadarrEvent::EditAllIndexerSettings(Some(params))).into())
|
||||||
"All indexer settings updated"
|
.await?;
|
||||||
);
|
"All indexer settings updated".to_owned()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RadarrEditCommand::Collection {
|
RadarrEditCommand::Collection {
|
||||||
@@ -417,11 +418,11 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH
|
|||||||
root_folder_path,
|
root_folder_path,
|
||||||
search_on_add: search_on_add_value,
|
search_on_add: search_on_add_value,
|
||||||
};
|
};
|
||||||
execute_network_event!(
|
self
|
||||||
self,
|
.network
|
||||||
RadarrEvent::EditCollection(Some(edit_collection_params)),
|
.handle_network_event((RadarrEvent::EditCollection(Some(edit_collection_params))).into())
|
||||||
"Collection Updated"
|
.await?;
|
||||||
);
|
"Collection updated".to_owned()
|
||||||
}
|
}
|
||||||
RadarrEditCommand::Indexer {
|
RadarrEditCommand::Indexer {
|
||||||
indexer_id,
|
indexer_id,
|
||||||
@@ -458,11 +459,11 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH
|
|||||||
clear_tags,
|
clear_tags,
|
||||||
};
|
};
|
||||||
|
|
||||||
execute_network_event!(
|
self
|
||||||
self,
|
.network
|
||||||
RadarrEvent::EditIndexer(Some(edit_indexer_params)),
|
.handle_network_event((RadarrEvent::EditIndexer(Some(edit_indexer_params))).into())
|
||||||
"Indexer updated"
|
.await?;
|
||||||
);
|
"Indexer updated".to_owned()
|
||||||
}
|
}
|
||||||
RadarrEditCommand::Movie {
|
RadarrEditCommand::Movie {
|
||||||
movie_id,
|
movie_id,
|
||||||
@@ -485,14 +486,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH
|
|||||||
clear_tags,
|
clear_tags,
|
||||||
};
|
};
|
||||||
|
|
||||||
execute_network_event!(
|
self
|
||||||
self,
|
.network
|
||||||
RadarrEvent::EditMovie(Some(edit_movie_params)),
|
.handle_network_event((RadarrEvent::EditMovie(Some(edit_movie_params))).into())
|
||||||
"Movie updated"
|
.await?;
|
||||||
);
|
"Movie Updated".to_owned()
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use tokio::sync::Mutex;
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
execute_network_event,
|
|
||||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
network::{radarr_network::RadarrEvent, NetworkTrait},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,28 +71,52 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrGetCommand> for RadarrGetCommandHan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle(self) -> Result<()> {
|
async fn handle(self) -> Result<String> {
|
||||||
match self.command {
|
let result = match self.command {
|
||||||
RadarrGetCommand::AllIndexerSettings => {
|
RadarrGetCommand::AllIndexerSettings => {
|
||||||
execute_network_event!(self, RadarrEvent::GetAllIndexerSettings);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetAllIndexerSettings).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrGetCommand::HostConfig => {
|
RadarrGetCommand::HostConfig => {
|
||||||
execute_network_event!(self, RadarrEvent::GetHostConfig);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetHostConfig).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrGetCommand::MovieDetails { movie_id } => {
|
RadarrGetCommand::MovieDetails { movie_id } => {
|
||||||
execute_network_event!(self, RadarrEvent::GetMovieDetails(Some(movie_id)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetMovieDetails(Some(movie_id))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrGetCommand::MovieHistory { movie_id } => {
|
RadarrGetCommand::MovieHistory { movie_id } => {
|
||||||
execute_network_event!(self, RadarrEvent::GetMovieHistory(Some(movie_id)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetMovieHistory(Some(movie_id))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrGetCommand::SecurityConfig => {
|
RadarrGetCommand::SecurityConfig => {
|
||||||
execute_network_event!(self, RadarrEvent::GetSecurityConfig);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetSecurityConfig).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrGetCommand::SystemStatus => {
|
RadarrGetCommand::SystemStatus => {
|
||||||
execute_network_event!(self, RadarrEvent::GetStatus);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetStatus).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use tokio::sync::Mutex;
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
execute_network_event,
|
|
||||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
network::{radarr_network::RadarrEvent, NetworkTrait},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -87,19 +86,35 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrListCommand> for RadarrListCommandH
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle(self) -> Result<()> {
|
async fn handle(self) -> Result<String> {
|
||||||
match self.command {
|
let result = match self.command {
|
||||||
RadarrListCommand::Blocklist => {
|
RadarrListCommand::Blocklist => {
|
||||||
execute_network_event!(self, RadarrEvent::GetBlocklist);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetBlocklist).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrListCommand::Collections => {
|
RadarrListCommand::Collections => {
|
||||||
execute_network_event!(self, RadarrEvent::GetCollections);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetCollections).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrListCommand::Downloads => {
|
RadarrListCommand::Downloads => {
|
||||||
execute_network_event!(self, RadarrEvent::GetDownloads);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetDownloads).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrListCommand::Indexers => {
|
RadarrListCommand::Indexers => {
|
||||||
execute_network_event!(self, RadarrEvent::GetIndexers);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetIndexers).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrListCommand::Logs {
|
RadarrListCommand::Logs {
|
||||||
events,
|
events,
|
||||||
@@ -113,39 +128,69 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrListCommand> for RadarrListCommandH
|
|||||||
if output_in_log_format {
|
if output_in_log_format {
|
||||||
let log_lines = self.app.lock().await.data.radarr_data.logs.items.clone();
|
let log_lines = self.app.lock().await.data.radarr_data.logs.items.clone();
|
||||||
|
|
||||||
let json = serde_json::to_string_pretty(&log_lines)?;
|
serde_json::to_string_pretty(&log_lines)?
|
||||||
println!("{}", json);
|
|
||||||
} else {
|
} else {
|
||||||
let json = serde_json::to_string_pretty(&logs)?;
|
serde_json::to_string_pretty(&logs)?
|
||||||
println!("{}", json);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RadarrListCommand::Movies => {
|
RadarrListCommand::Movies => {
|
||||||
execute_network_event!(self, RadarrEvent::GetMovies);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetMovies).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrListCommand::MovieCredits { movie_id } => {
|
RadarrListCommand::MovieCredits { movie_id } => {
|
||||||
execute_network_event!(self, RadarrEvent::GetMovieCredits(Some(movie_id)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetMovieCredits(Some(movie_id))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrListCommand::QualityProfiles => {
|
RadarrListCommand::QualityProfiles => {
|
||||||
execute_network_event!(self, RadarrEvent::GetQualityProfiles);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetQualityProfiles).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrListCommand::QueuedEvents => {
|
RadarrListCommand::QueuedEvents => {
|
||||||
execute_network_event!(self, RadarrEvent::GetQueuedEvents);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetQueuedEvents).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrListCommand::RootFolders => {
|
RadarrListCommand::RootFolders => {
|
||||||
execute_network_event!(self, RadarrEvent::GetRootFolders);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetRootFolders).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrListCommand::Tags => {
|
RadarrListCommand::Tags => {
|
||||||
execute_network_event!(self, RadarrEvent::GetTags);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetTags).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrListCommand::Tasks => {
|
RadarrListCommand::Tasks => {
|
||||||
execute_network_event!(self, RadarrEvent::GetTasks);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetTasks).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrListCommand::Updates => {
|
RadarrListCommand::Updates => {
|
||||||
execute_network_event!(self, RadarrEvent::GetUpdates);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetUpdates).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-13
@@ -12,7 +12,6 @@ use tokio::sync::Mutex;
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
|
|
||||||
use crate::cli::CliCommandHandler;
|
use crate::cli::CliCommandHandler;
|
||||||
use crate::execute_network_event;
|
|
||||||
use crate::models::radarr_models::{ReleaseDownloadBody, TaskName};
|
use crate::models::radarr_models::{ReleaseDownloadBody, TaskName};
|
||||||
use crate::network::radarr_network::RadarrEvent;
|
use crate::network::radarr_network::RadarrEvent;
|
||||||
use crate::network::NetworkTrait;
|
use crate::network::NetworkTrait;
|
||||||
@@ -155,8 +154,8 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrCommand> for RadarrCliHandler<'a, '
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle(self) -> Result<()> {
|
async fn handle(self) -> Result<String> {
|
||||||
match self.command {
|
let result = match self.command {
|
||||||
RadarrCommand::Add(add_command) => {
|
RadarrCommand::Add(add_command) => {
|
||||||
RadarrAddCommandHandler::with(self.app, add_command, self.network)
|
RadarrAddCommandHandler::with(self.app, add_command, self.network)
|
||||||
.handle()
|
.handle()
|
||||||
@@ -192,7 +191,11 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrCommand> for RadarrCliHandler<'a, '
|
|||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::GetBlocklist.into())
|
.handle_network_event(RadarrEvent::GetBlocklist.into())
|
||||||
.await?;
|
.await?;
|
||||||
execute_network_event!(self, RadarrEvent::ClearBlocklist);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::ClearBlocklist).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrCommand::DownloadRelease {
|
RadarrCommand::DownloadRelease {
|
||||||
guid,
|
guid,
|
||||||
@@ -204,29 +207,57 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrCommand> for RadarrCliHandler<'a, '
|
|||||||
indexer_id,
|
indexer_id,
|
||||||
movie_id,
|
movie_id,
|
||||||
};
|
};
|
||||||
execute_network_event!(self, RadarrEvent::DownloadRelease(Some(params)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::DownloadRelease(Some(params))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrCommand::ManualSearch { movie_id } => {
|
RadarrCommand::ManualSearch { movie_id } => {
|
||||||
println!("Searching for releases. This may take a minute...");
|
println!("Searching for releases. This may take a minute...");
|
||||||
execute_network_event!(self, RadarrEvent::GetReleases(Some(movie_id)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::GetReleases(Some(movie_id))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrCommand::SearchNewMovie { query } => {
|
RadarrCommand::SearchNewMovie { query } => {
|
||||||
execute_network_event!(self, RadarrEvent::SearchNewMovie(Some(query)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::SearchNewMovie(Some(query))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrCommand::StartTask { task_name } => {
|
RadarrCommand::StartTask { task_name } => {
|
||||||
execute_network_event!(self, RadarrEvent::StartTask(Some(task_name)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::StartTask(Some(task_name))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrCommand::TestIndexer { indexer_id } => {
|
RadarrCommand::TestIndexer { indexer_id } => {
|
||||||
execute_network_event!(self, RadarrEvent::TestIndexer(Some(indexer_id)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::TestIndexer(Some(indexer_id))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrCommand::TestAllIndexers => {
|
RadarrCommand::TestAllIndexers => {
|
||||||
execute_network_event!(self, RadarrEvent::TestAllIndexers);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::TestAllIndexers).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrCommand::TriggerAutomaticSearch { movie_id } => {
|
RadarrCommand::TriggerAutomaticSearch { movie_id } => {
|
||||||
execute_network_event!(self, RadarrEvent::TriggerAutomaticSearch(Some(movie_id)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::TriggerAutomaticSearch(Some(movie_id))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use tokio::sync::Mutex;
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
execute_network_event,
|
|
||||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
network::{radarr_network::RadarrEvent, NetworkTrait},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -63,22 +62,38 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrRefreshCommand>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle(self) -> Result<()> {
|
async fn handle(self) -> Result<String> {
|
||||||
match self.command {
|
let result = match self.command {
|
||||||
RadarrRefreshCommand::AllMovies => {
|
RadarrRefreshCommand::AllMovies => {
|
||||||
execute_network_event!(self, RadarrEvent::UpdateAllMovies);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::UpdateAllMovies).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrRefreshCommand::Collections => {
|
RadarrRefreshCommand::Collections => {
|
||||||
execute_network_event!(self, RadarrEvent::UpdateCollections);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::UpdateCollections).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrRefreshCommand::Downloads => {
|
RadarrRefreshCommand::Downloads => {
|
||||||
execute_network_event!(self, RadarrEvent::UpdateDownloads);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::UpdateDownloads).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrRefreshCommand::Movie { movie_id } => {
|
RadarrRefreshCommand::Movie { movie_id } => {
|
||||||
execute_network_event!(self, RadarrEvent::UpdateAndScan(Some(movie_id)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((RadarrEvent::UpdateAndScan(Some(movie_id))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use tokio::sync::Mutex;
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
execute_network_event,
|
|
||||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -55,16 +54,17 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrDeleteCommand> for SonarrDeleteComm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle(self) -> Result<()> {
|
async fn handle(self) -> Result<String> {
|
||||||
match self.command {
|
let resp = match self.command {
|
||||||
SonarrDeleteCommand::BlocklistItem { blocklist_item_id } => {
|
SonarrDeleteCommand::BlocklistItem { blocklist_item_id } => {
|
||||||
execute_network_event!(
|
let resp = self
|
||||||
self,
|
.network
|
||||||
SonarrEvent::DeleteBlocklistItem(Some(blocklist_item_id))
|
.handle_network_event((SonarrEvent::DeleteBlocklistItem(Some(blocklist_item_id))).into())
|
||||||
);
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(resp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use tokio::sync::Mutex;
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
execute_network_event,
|
|
||||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,28 +71,52 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrGetCommand> for SonarrGetCommandHan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle(self) -> Result<()> {
|
async fn handle(self) -> Result<String> {
|
||||||
match self.command {
|
let result = match self.command {
|
||||||
SonarrGetCommand::AllIndexerSettings => {
|
SonarrGetCommand::AllIndexerSettings => {
|
||||||
execute_network_event!(self, SonarrEvent::GetAllIndexerSettings);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((SonarrEvent::GetAllIndexerSettings).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrGetCommand::EpisodeDetails { episode_id } => {
|
SonarrGetCommand::EpisodeDetails { episode_id } => {
|
||||||
execute_network_event!(self, SonarrEvent::GetEpisodeDetails(Some(episode_id)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((SonarrEvent::GetEpisodeDetails(Some(episode_id))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrGetCommand::HostConfig => {
|
SonarrGetCommand::HostConfig => {
|
||||||
execute_network_event!(self, SonarrEvent::GetHostConfig);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((SonarrEvent::GetHostConfig).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrGetCommand::SecurityConfig => {
|
SonarrGetCommand::SecurityConfig => {
|
||||||
execute_network_event!(self, SonarrEvent::GetSecurityConfig);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((SonarrEvent::GetSecurityConfig).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrGetCommand::SeriesDetails { series_id } => {
|
SonarrGetCommand::SeriesDetails { series_id } => {
|
||||||
execute_network_event!(self, SonarrEvent::GetSeriesDetails(Some(series_id)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((SonarrEvent::GetSeriesDetails(Some(series_id))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrGetCommand::SystemStatus => {
|
SonarrGetCommand::SystemStatus => {
|
||||||
execute_network_event!(self, SonarrEvent::GetStatus);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((SonarrEvent::GetStatus).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use tokio::sync::Mutex;
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
execute_network_event,
|
|
||||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,22 +90,42 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrListCommand> for SonarrListCommandH
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle(self) -> Result<()> {
|
async fn handle(self) -> Result<String> {
|
||||||
match self.command {
|
let result = match self.command {
|
||||||
SonarrListCommand::Blocklist => {
|
SonarrListCommand::Blocklist => {
|
||||||
execute_network_event!(self, SonarrEvent::GetBlocklist);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((SonarrEvent::GetBlocklist).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrListCommand::Downloads => {
|
SonarrListCommand::Downloads => {
|
||||||
execute_network_event!(self, SonarrEvent::GetDownloads);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((SonarrEvent::GetDownloads).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrListCommand::Episodes { series_id } => {
|
SonarrListCommand::Episodes { series_id } => {
|
||||||
execute_network_event!(self, SonarrEvent::GetEpisodes(Some(series_id)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((SonarrEvent::GetEpisodes(Some(series_id))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrListCommand::History { events: items } => {
|
SonarrListCommand::History { events: items } => {
|
||||||
execute_network_event!(self, SonarrEvent::GetHistory(Some(items)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((SonarrEvent::GetHistory(Some(items))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrListCommand::Indexers => {
|
SonarrListCommand::Indexers => {
|
||||||
execute_network_event!(self, SonarrEvent::GetIndexers);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((SonarrEvent::GetIndexers).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrListCommand::Logs {
|
SonarrListCommand::Logs {
|
||||||
events,
|
events,
|
||||||
@@ -120,27 +139,41 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrListCommand> for SonarrListCommandH
|
|||||||
if output_in_log_format {
|
if output_in_log_format {
|
||||||
let log_lines = self.app.lock().await.data.sonarr_data.logs.items.clone();
|
let log_lines = self.app.lock().await.data.sonarr_data.logs.items.clone();
|
||||||
|
|
||||||
let json = serde_json::to_string_pretty(&log_lines)?;
|
serde_json::to_string_pretty(&log_lines)?
|
||||||
println!("{}", json);
|
|
||||||
} else {
|
} else {
|
||||||
let json = serde_json::to_string_pretty(&logs)?;
|
serde_json::to_string_pretty(&logs)?
|
||||||
println!("{}", json);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SonarrListCommand::QualityProfiles => {
|
SonarrListCommand::QualityProfiles => {
|
||||||
execute_network_event!(self, SonarrEvent::GetQualityProfiles);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((SonarrEvent::GetQualityProfiles).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrListCommand::QueuedEvents => {
|
SonarrListCommand::QueuedEvents => {
|
||||||
execute_network_event!(self, SonarrEvent::GetQueuedEvents);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((SonarrEvent::GetQueuedEvents).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrListCommand::Series => {
|
SonarrListCommand::Series => {
|
||||||
execute_network_event!(self, SonarrEvent::ListSeries);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((SonarrEvent::ListSeries).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrListCommand::SeriesHistory { series_id } => {
|
SonarrListCommand::SeriesHistory { series_id } => {
|
||||||
execute_network_event!(self, SonarrEvent::GetSeriesHistory(Some(series_id)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event((SonarrEvent::GetSeriesHistory(Some(series_id))).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-11
@@ -9,7 +9,6 @@ use tokio::sync::Mutex;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
execute_network_event,
|
|
||||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,8 +90,8 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, '
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle(self) -> Result<()> {
|
async fn handle(self) -> Result<String> {
|
||||||
match self.command {
|
let result = match self.command {
|
||||||
SonarrCommand::Delete(delete_command) => {
|
SonarrCommand::Delete(delete_command) => {
|
||||||
SonarrDeleteCommandHandler::with(self.app, delete_command, self.network)
|
SonarrDeleteCommandHandler::with(self.app, delete_command, self.network)
|
||||||
.handle()
|
.handle()
|
||||||
@@ -113,24 +112,35 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, '
|
|||||||
.network
|
.network
|
||||||
.handle_network_event(SonarrEvent::GetBlocklist.into())
|
.handle_network_event(SonarrEvent::GetBlocklist.into())
|
||||||
.await?;
|
.await?;
|
||||||
execute_network_event!(self, SonarrEvent::ClearBlocklist);
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(SonarrEvent::ClearBlocklist.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrCommand::ManualEpisodeSearch { episode_id } => {
|
SonarrCommand::ManualEpisodeSearch { episode_id } => {
|
||||||
println!("Searching for episode releases. This may take a minute...");
|
println!("Searching for episode releases. This may take a minute...");
|
||||||
execute_network_event!(self, SonarrEvent::GetEpisodeReleases(Some(episode_id)));
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(SonarrEvent::GetEpisodeReleases(Some(episode_id)).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrCommand::ManualSeasonSearch {
|
SonarrCommand::ManualSeasonSearch {
|
||||||
series_id,
|
series_id,
|
||||||
season_number,
|
season_number,
|
||||||
} => {
|
} => {
|
||||||
println!("Searching for season releases. This may take a minute...");
|
println!("Searching for season releases. This may take a minute...");
|
||||||
execute_network_event!(
|
let resp = self
|
||||||
self,
|
.network
|
||||||
SonarrEvent::GetSeasonReleases(Some((series_id, season_number)))
|
.handle_network_event(
|
||||||
);
|
SonarrEvent::GetSeasonReleases(Some((series_id, season_number))).into(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-79
@@ -1,20 +1,13 @@
|
|||||||
#![warn(rust_2018_idioms)]
|
#![warn(rust_2018_idioms)]
|
||||||
|
|
||||||
use std::fs::{self, File};
|
use anyhow::Result;
|
||||||
use std::io::BufReader;
|
|
||||||
use std::panic::PanicHookInfo;
|
use std::panic::PanicHookInfo;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
|
||||||
use std::{io, panic, process};
|
use std::{io, panic, process};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use clap::{crate_authors, crate_description, crate_name, crate_version, CommandFactory, Parser};
|
||||||
use anyhow::Result;
|
|
||||||
use app::{log_and_print_error, AppConfig};
|
|
||||||
use clap::{
|
|
||||||
command, crate_authors, crate_description, crate_name, crate_version, CommandFactory, Parser,
|
|
||||||
};
|
|
||||||
use clap_complete::generate;
|
use clap_complete::generate;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use crossterm::execute;
|
use crossterm::execute;
|
||||||
@@ -25,14 +18,17 @@ use log::{error, warn};
|
|||||||
use network::NetworkTrait;
|
use network::NetworkTrait;
|
||||||
use ratatui::backend::CrosstermBackend;
|
use ratatui::backend::CrosstermBackend;
|
||||||
use ratatui::Terminal;
|
use ratatui::Terminal;
|
||||||
use reqwest::{Certificate, Client};
|
use reqwest::Client;
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tokio::sync::mpsc::Receiver;
|
use tokio::sync::mpsc::Receiver;
|
||||||
use tokio::sync::{mpsc, Mutex};
|
use tokio::sync::{mpsc, Mutex};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use utils::tail_logs;
|
use utils::{
|
||||||
|
build_network_client, load_config, render_spinner, start_cli_no_spinner, start_cli_with_spinner,
|
||||||
|
tail_logs,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::app::App;
|
use crate::app::{App, AppConfig};
|
||||||
use crate::cli::Command;
|
use crate::cli::Command;
|
||||||
use crate::event::input_event::{Events, InputEvent};
|
use crate::event::input_event::{Events, InputEvent};
|
||||||
use crate::event::Key;
|
use crate::event::Key;
|
||||||
@@ -67,6 +63,13 @@ mod utils;
|
|||||||
struct Cli {
|
struct Cli {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
command: Option<Command>,
|
command: Option<Command>,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
global = true,
|
||||||
|
env = "MANAGARR_DISABLE_SPINNER",
|
||||||
|
help = "Disable the spinner (can sometimes make parsing output challenging)"
|
||||||
|
)]
|
||||||
|
disable_spinner: bool,
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
global = true,
|
global = true,
|
||||||
@@ -91,6 +94,7 @@ async fn main() -> Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
confy::load("managarr", "config")?
|
confy::load("managarr", "config")?
|
||||||
};
|
};
|
||||||
|
let spinner_disabled = args.disable_spinner;
|
||||||
config.validate();
|
config.validate();
|
||||||
let reqwest_client = build_network_client(&config);
|
let reqwest_client = build_network_client(&config);
|
||||||
let (sync_network_tx, sync_network_rx) = mpsc::channel(500);
|
let (sync_network_tx, sync_network_rx) = mpsc::channel(500);
|
||||||
@@ -113,14 +117,10 @@ 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(_) => {
|
||||||
config.verify_config_present_for_cli(&command);
|
if spinner_disabled {
|
||||||
app.lock().await.cli_mode = true;
|
start_cli_no_spinner(config, reqwest_client, cancellation_token, app, command).await;
|
||||||
let app_nw = Arc::clone(&app);
|
} else {
|
||||||
let mut network = Network::new(&app_nw, cancellation_token, reqwest_client);
|
start_cli_with_spinner(config, reqwest_client, cancellation_token, app, command).await;
|
||||||
|
|
||||||
if let Err(e) = cli::handle_command(&app, command, &mut network).await {
|
|
||||||
eprintln!("error: {}", e.to_string().red());
|
|
||||||
process::exit(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Completions { shell } => {
|
Command::Completions { shell } => {
|
||||||
@@ -237,65 +237,6 @@ fn panic_hook(info: &PanicHookInfo<'_>) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_config(path: &str) -> Result<AppConfig> {
|
|
||||||
let file = File::open(path).map_err(|e| anyhow!(e))?;
|
|
||||||
let reader = BufReader::new(file);
|
|
||||||
let config = serde_yaml::from_reader(reader)?;
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_network_client(config: &AppConfig) -> Client {
|
|
||||||
let mut client_builder = Client::builder()
|
|
||||||
.pool_max_idle_per_host(10)
|
|
||||||
.http2_keep_alive_interval(Duration::from_secs(5))
|
|
||||||
.tcp_keepalive(Duration::from_secs(5));
|
|
||||||
|
|
||||||
if let Some(radarr_config) = &config.radarr {
|
|
||||||
if let Some(ref cert_path) = &radarr_config.ssl_cert_path {
|
|
||||||
let cert = create_cert(cert_path, "Radarr");
|
|
||||||
client_builder = client_builder.add_root_certificate(cert);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(sonarr_config) = &config.sonarr {
|
|
||||||
if let Some(ref cert_path) = &sonarr_config.ssl_cert_path {
|
|
||||||
let cert = create_cert(cert_path, "Sonarr");
|
|
||||||
client_builder = client_builder.add_root_certificate(cert);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match client_builder.build() {
|
|
||||||
Ok(client) => client,
|
|
||||||
Err(e) => {
|
|
||||||
error!("Unable to create reqwest client: {}", e);
|
|
||||||
eprintln!("error: {}", e.to_string().red());
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_cert(cert_path: &String, servarr_name: &str) -> Certificate {
|
|
||||||
match fs::read(cert_path) {
|
|
||||||
Ok(cert) => match Certificate::from_pem(&cert) {
|
|
||||||
Ok(certificate) => certificate,
|
|
||||||
Err(_) => {
|
|
||||||
log_and_print_error(format!(
|
|
||||||
"Unable to read the specified {} SSL certificate",
|
|
||||||
servarr_name
|
|
||||||
));
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(_) => {
|
|
||||||
log_and_print_error(format!(
|
|
||||||
"Unable to open specified {} SSL certificate",
|
|
||||||
servarr_name
|
|
||||||
));
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
fn panic_hook(info: &PanicHookInfo<'_>) {
|
fn panic_hook(info: &PanicHookInfo<'_>) {
|
||||||
use human_panic::{handle_dump, metadata, print_msg};
|
use human_panic::{handle_dump, metadata, print_msg};
|
||||||
|
|||||||
+69
-13
@@ -14,7 +14,7 @@ use super::{
|
|||||||
HostConfig, Indexer, Language, LogResponse, QualityProfile, QualityWrapper, QueueEvent,
|
HostConfig, Indexer, Language, LogResponse, QualityProfile, QualityWrapper, QueueEvent,
|
||||||
Release, SecurityConfig,
|
Release, SecurityConfig,
|
||||||
},
|
},
|
||||||
HorizontallyScrollableText, Serdeable,
|
EnumDisplayStyle, HorizontallyScrollableText, Serdeable,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -43,7 +43,7 @@ pub struct BlocklistResponse {
|
|||||||
pub records: Vec<BlocklistItem>,
|
pub records: Vec<BlocklistItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
|
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DownloadRecord {
|
pub struct DownloadRecord {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
@@ -52,16 +52,18 @@ pub struct DownloadRecord {
|
|||||||
pub id: i64,
|
pub id: i64,
|
||||||
#[serde(deserialize_with = "super::from_i64")]
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
pub episode_id: i64,
|
pub episode_id: i64,
|
||||||
#[serde(deserialize_with = "super::from_i64")]
|
#[serde(deserialize_with = "super::from_f64")]
|
||||||
pub size: i64,
|
pub size: f64,
|
||||||
#[serde(deserialize_with = "super::from_i64")]
|
#[serde(deserialize_with = "super::from_f64")]
|
||||||
pub sizeleft: i64,
|
pub sizeleft: f64,
|
||||||
pub output_path: Option<HorizontallyScrollableText>,
|
pub output_path: Option<HorizontallyScrollableText>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub indexer: String,
|
pub indexer: String,
|
||||||
pub download_client: String,
|
pub download_client: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Eq for DownloadRecord {}
|
||||||
|
|
||||||
#[derive(Default, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
|
#[derive(Default, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DownloadsResponse {
|
pub struct DownloadsResponse {
|
||||||
@@ -242,8 +244,8 @@ impl Display for SeriesType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SeriesType {
|
impl<'a> EnumDisplayStyle<'a> for SeriesType {
|
||||||
pub fn to_display_str<'a>(self) -> &'a str {
|
fn to_display_str(self) -> &'a str {
|
||||||
match self {
|
match self {
|
||||||
SeriesType::Standard => "Standard",
|
SeriesType::Standard => "Standard",
|
||||||
SeriesType::Daily => "Daily",
|
SeriesType::Daily => "Daily",
|
||||||
@@ -293,8 +295,8 @@ impl Display for SeriesStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SeriesStatus {
|
impl<'a> EnumDisplayStyle<'a> for SeriesStatus {
|
||||||
pub fn to_display_str<'a>(self) -> &'a str {
|
fn to_display_str(self) -> &'a str {
|
||||||
match self {
|
match self {
|
||||||
SeriesStatus::Continuing => "Continuing",
|
SeriesStatus::Continuing => "Continuing",
|
||||||
SeriesStatus::Ended => "Ended",
|
SeriesStatus::Ended => "Ended",
|
||||||
@@ -313,8 +315,62 @@ pub struct SonarrHistoryWrapper {
|
|||||||
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SonarrHistoryData {
|
pub struct SonarrHistoryData {
|
||||||
pub dropped_path: String,
|
pub dropped_path: Option<String>,
|
||||||
pub imported_path: String,
|
pub imported_path: Option<String>,
|
||||||
|
pub indexer: Option<String>,
|
||||||
|
pub release_group: Option<String>,
|
||||||
|
pub series_match_type: Option<String>,
|
||||||
|
pub nzb_info_url: Option<String>,
|
||||||
|
pub download_client_name: Option<String>,
|
||||||
|
pub age: Option<String>,
|
||||||
|
pub published_date: Option<DateTime<Utc>>,
|
||||||
|
pub message: Option<String>,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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)]
|
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -326,7 +382,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 language: Language,
|
||||||
pub date: DateTime<Utc>,
|
pub date: DateTime<Utc>,
|
||||||
pub event_type: String,
|
pub event_type: String,
|
||||||
pub data: SonarrHistoryData,
|
pub data: SonarrHistoryData,
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ mod tests {
|
|||||||
},
|
},
|
||||||
sonarr_models::{
|
sonarr_models::{
|
||||||
BlocklistItem, BlocklistResponse, DownloadRecord, DownloadsResponse, Episode,
|
BlocklistItem, BlocklistResponse, DownloadRecord, DownloadsResponse, Episode,
|
||||||
IndexerSettings, Series, SeriesStatus, SeriesType, SonarrHistoryItem, SonarrSerdeable,
|
IndexerSettings, Series, SeriesStatus, SeriesType, SonarrHistoryEventType, SonarrHistoryItem,
|
||||||
SystemStatus,
|
SonarrSerdeable, SystemStatus,
|
||||||
},
|
},
|
||||||
Serdeable,
|
EnumDisplayStyle, Serdeable,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -56,6 +56,66 @@ mod tests {
|
|||||||
assert_str_eq!(SeriesType::Anime.to_display_str(), "Anime");
|
assert_str_eq!(SeriesType::Anime.to_display_str(), "Anime");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sonarr_history_event_type_display() {
|
||||||
|
assert_str_eq!(SonarrHistoryEventType::Unknown.to_string(), "unknown",);
|
||||||
|
assert_str_eq!(SonarrHistoryEventType::Grabbed.to_string(), "grabbed",);
|
||||||
|
assert_str_eq!(
|
||||||
|
SonarrHistoryEventType::SeriesFolderImported.to_string(),
|
||||||
|
"seriesFolderImported",
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
SonarrHistoryEventType::DownloadFolderImported.to_string(),
|
||||||
|
"downloadFolderImported",
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
SonarrHistoryEventType::DownloadFailed.to_string(),
|
||||||
|
"downloadFailed",
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
SonarrHistoryEventType::EpisodeFileDeleted.to_string(),
|
||||||
|
"episodeFileDeleted",
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
SonarrHistoryEventType::EpisodeFileRenamed.to_string(),
|
||||||
|
"episodeFileRenamed",
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
SonarrHistoryEventType::DownloadIgnored.to_string(),
|
||||||
|
"downloadIgnored",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sonarr_history_event_type_to_display_str() {
|
||||||
|
assert_str_eq!(SonarrHistoryEventType::Unknown.to_display_str(), "Unknown",);
|
||||||
|
assert_str_eq!(SonarrHistoryEventType::Grabbed.to_display_str(), "Grabbed",);
|
||||||
|
assert_str_eq!(
|
||||||
|
SonarrHistoryEventType::SeriesFolderImported.to_display_str(),
|
||||||
|
"Series Folder Imported",
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
SonarrHistoryEventType::DownloadFolderImported.to_display_str(),
|
||||||
|
"Download Folder Imported",
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
SonarrHistoryEventType::DownloadFailed.to_display_str(),
|
||||||
|
"Download Failed",
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
SonarrHistoryEventType::EpisodeFileDeleted.to_display_str(),
|
||||||
|
"Episode File Deleted",
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
SonarrHistoryEventType::EpisodeFileRenamed.to_display_str(),
|
||||||
|
"Episode File Renamed",
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
SonarrHistoryEventType::DownloadIgnored.to_display_str(),
|
||||||
|
"Download Ignored",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sonarr_serdeable_from() {
|
fn test_sonarr_serdeable_from() {
|
||||||
let sonarr_serdeable = SonarrSerdeable::Value(json!({}));
|
let sonarr_serdeable = SonarrSerdeable::Value(json!({}));
|
||||||
|
|||||||
+136
-1
@@ -2,13 +2,25 @@ use std::fs::{self, File};
|
|||||||
use std::io::{BufRead, BufReader, Seek, SeekFrom};
|
use std::io::{BufRead, BufReader, Seek, SeekFrom};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process;
|
use std::process;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use anyhow::Result;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use log::LevelFilter;
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
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;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use reqwest::{Certificate, Client};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
|
use crate::app::{log_and_print_error, App, AppConfig};
|
||||||
|
use crate::cli::{self, Command};
|
||||||
|
use crate::network::Network;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "utils_tests.rs"]
|
#[path = "utils_tests.rs"]
|
||||||
@@ -122,3 +134,126 @@ fn colorize_log_line(line: &str, re: &Regex) -> String {
|
|||||||
line.to_string()
|
line.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn load_config(path: &str) -> Result<AppConfig> {
|
||||||
|
let file = File::open(path).map_err(|e| anyhow!(e))?;
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
let config = serde_yaml::from_reader(reader)?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn build_network_client(config: &AppConfig) -> Client {
|
||||||
|
let mut client_builder = Client::builder()
|
||||||
|
.pool_max_idle_per_host(10)
|
||||||
|
.http2_keep_alive_interval(Duration::from_secs(5))
|
||||||
|
.tcp_keepalive(Duration::from_secs(5));
|
||||||
|
|
||||||
|
if let Some(radarr_config) = &config.radarr {
|
||||||
|
if let Some(ref cert_path) = &radarr_config.ssl_cert_path {
|
||||||
|
let cert = create_cert(cert_path, "Radarr");
|
||||||
|
client_builder = client_builder.add_root_certificate(cert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(sonarr_config) = &config.sonarr {
|
||||||
|
if let Some(ref cert_path) = &sonarr_config.ssl_cert_path {
|
||||||
|
let cert = create_cert(cert_path, "Sonarr");
|
||||||
|
client_builder = client_builder.add_root_certificate(cert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match client_builder.build() {
|
||||||
|
Ok(client) => client,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Unable to create reqwest client: {}", e);
|
||||||
|
eprintln!("error: {}", e.to_string().red());
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn create_cert(cert_path: &String, servarr_name: &str) -> Certificate {
|
||||||
|
match fs::read(cert_path) {
|
||||||
|
Ok(cert) => match Certificate::from_pem(&cert) {
|
||||||
|
Ok(certificate) => certificate,
|
||||||
|
Err(_) => {
|
||||||
|
log_and_print_error(format!(
|
||||||
|
"Unable to read the specified {} SSL certificate",
|
||||||
|
servarr_name
|
||||||
|
));
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
log_and_print_error(format!(
|
||||||
|
"Unable to open specified {} SSL certificate",
|
||||||
|
servarr_name
|
||||||
|
));
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn render_spinner() -> ProgressBar {
|
||||||
|
let pb = ProgressBar::new_spinner();
|
||||||
|
pb.enable_steady_tick(Duration::from_millis(60));
|
||||||
|
pb.set_style(
|
||||||
|
ProgressStyle::with_template("{spinner:.blue}")
|
||||||
|
.unwrap()
|
||||||
|
.tick_strings(&[
|
||||||
|
"⢀⠀", "⡀⠀", "⠄⠀", "⢂⠀", "⡂⠀", "⠅⠀", "⢃⠀", "⡃⠀", "⠍⠀", "⢋⠀", "⡋⠀", "⠍⠁", "⢋⠁", "⡋⠁", "⠍⠉",
|
||||||
|
"⠋⠉", "⠋⠉", "⠉⠙", "⠉⠙", "⠉⠩", "⠈⢙", "⠈⡙", "⢈⠩", "⡀⢙", "⠄⡙", "⢂⠩", "⡂⢘", "⠅⡘", "⢃⠨", "⡃⢐",
|
||||||
|
"⠍⡐", "⢋⠠", "⡋⢀", "⠍⡁", "⢋⠁", "⡋⠁", "⠍⠉", "⠋⠉", "⠋⠉", "⠉⠙", "⠉⠙", "⠉⠩", "⠈⢙", "⠈⡙", "⠈⠩",
|
||||||
|
"⠀⢙", "⠀⡙", "⠀⠩", "⠀⢘", "⠀⡘", "⠀⠨", "⠀⢐", "⠀⡐", "⠀⠠", "⠀⢀", "⠀⡀",
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
pb.set_message("Querying...");
|
||||||
|
pb
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn start_cli_with_spinner(
|
||||||
|
config: AppConfig,
|
||||||
|
reqwest_client: Client,
|
||||||
|
cancellation_token: CancellationToken,
|
||||||
|
app: Arc<Mutex<App<'_>>>,
|
||||||
|
command: Command,
|
||||||
|
) {
|
||||||
|
config.verify_config_present_for_cli(&command);
|
||||||
|
app.lock().await.cli_mode = true;
|
||||||
|
let pb = render_spinner();
|
||||||
|
let app_nw = Arc::clone(&app);
|
||||||
|
let mut network = Network::new(&app_nw, cancellation_token, reqwest_client);
|
||||||
|
match cli::handle_command(&app, command, &mut network).await {
|
||||||
|
Ok(output) => {
|
||||||
|
pb.finish();
|
||||||
|
println!("{}", output);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
pb.finish();
|
||||||
|
eprintln!("error: {}", e.to_string().red());
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn start_cli_no_spinner(
|
||||||
|
config: AppConfig,
|
||||||
|
reqwest_client: Client,
|
||||||
|
cancellation_token: CancellationToken,
|
||||||
|
app: Arc<Mutex<App<'_>>>,
|
||||||
|
command: Command,
|
||||||
|
) {
|
||||||
|
config.verify_config_present_for_cli(&command);
|
||||||
|
app.lock().await.cli_mode = true;
|
||||||
|
let app_nw = Arc::clone(&app);
|
||||||
|
let mut network = Network::new(&app_nw, cancellation_token, reqwest_client);
|
||||||
|
match cli::handle_command(&app, command, &mut network).await {
|
||||||
|
Ok(output) => {
|
||||||
|
println!("{}", output);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("error: {}", e.to_string().red());
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user