Compare commits

...

7 Commits

14 changed files with 382 additions and 42 deletions
+1
View File
@@ -0,0 +1 @@
github: Dark-Alex-17
+1 -1
View File
@@ -1,7 +1,7 @@
# Adapted from https://github.com/joshka/github-workflows/blob/main/.github/workflows/rust-release-plz.yml
# Thanks to joshka for permission to use this template!
name: Create Release PR and publish to crates.io
name: Create Release PR and Publish Release
permissions:
pull-requests: write
+7
View File
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.1.4](https://github.com/Dark-Alex-17/managarr/compare/v0.1.3...v0.1.4) - 2024-11-01
### Other
- Added the ability to fetch host configs and security configs to the CLI
- Updated README to be more clear about what features are supported [skip ci]
## [0.1.2](https://github.com/Dark-Alex-17/managarr/compare/v0.1.1...v0.1.2) - 2024-10-30
### Other
Generated
+1 -1
View File
@@ -1148,7 +1148,7 @@ dependencies = [
[[package]]
name = "managarr"
version = "0.1.2"
version = "0.1.4"
dependencies = [
"anyhow",
"assert_cmd",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "managarr"
version = "0.1.2"
version = "0.1.4"
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
description = "A TUI and CLI to manage your Servarrs"
keywords = ["managarr", "tui-rs", "dashboard", "servarr", "tui"]
+1 -1
View File
@@ -23,4 +23,4 @@ FROM debian:stable-slim
# Copy the compiled binary from the builder container
COPY --from=builder --chown=nonroot:nonroot /usr/src/managarr-temp/managarr /usr/local/bin
ENTRYPOINT [ "/usr/local/bin/managarr" ]
ENTRYPOINT [ "/usr/local/bin/managarr", "--disable-terminal-size-checks" ]
+27 -12
View File
@@ -17,14 +17,14 @@ Managarr is a TUI and CLI to help you manage your HTPC (Home Theater PC). Built
## What Servarrs are supported?
- ![radarr_logo](logos/radarr.png) [Radarr](https://wiki.servarr.com/radarr)
- ![sonarr_logo](logos/sonarr.png) [Sonarr](https://wiki.servarr.com/en/sonarr)
- ![readarr_logo](logos/readarr.png) [Readarr](https://wiki.servarr.com/en/readarr)
- ![lidarr_logo](logos/lidarr.png) [Lidarr](https://wiki.servarr.com/en/lidarr)
- ![prowlarr_logo](logos/prowlarr.png) [Prowlarr](https://wiki.servarr.com/en/prowlarr)
- ![whisparr_logo](logos/whisparr.png) [Whisparr](https://wiki.servarr.com/whisparr)
- ![bazarr_logo](logos/bazarr.png) [Bazarr](https://www.bazarr.media/)
- ![tautulli_logo](logos/tautulli.png) [Tautulli](https://tautulli.com/)
- [x] ![radarr_logo](logos/radarr.png) [Radarr](https://wiki.servarr.com/radarr)
- [ ] ![sonarr_logo](logos/sonarr.png) [Sonarr](https://wiki.servarr.com/en/sonarr)
- [ ] ![readarr_logo](logos/readarr.png) [Readarr](https://wiki.servarr.com/en/readarr)
- [ ] ![lidarr_logo](logos/lidarr.png) [Lidarr](https://wiki.servarr.com/en/lidarr)
- [ ] ![prowlarr_logo](logos/prowlarr.png) [Prowlarr](https://wiki.servarr.com/en/prowlarr)
- [ ] ![whisparr_logo](logos/whisparr.png) [Whisparr](https://wiki.servarr.com/whisparr)
- [ ] ![bazarr_logo](logos/bazarr.png) [Bazarr](https://www.bazarr.media/)
- [ ] ![tautulli_logo](logos/tautulli.png) [Tautulli](https://tautulli.com/)
## Try Before You Buy
To try out Managarr before linking it to your HTPC, you can use the purpose built [managarr-demo](https://github.com/Dark-Alex-17/managarr-demo) repository.
@@ -60,6 +60,7 @@ You can also clone this repo and run `make docker` to build a docker image local
- [x] View your library, downloads, collections, and blocklist
- [x] View details of a specific movie including description, history, downloaded file info, or the credits
- [x] View details of any collection and the movies in them
- [x] View your host and security configs from the CLI to programmatically fetch the API token, among other settings
- [x] Search your library or collections
- [x] Add movies to your library
- [x] Delete movies, downloads, and indexers
@@ -103,7 +104,8 @@ You can also clone this repo and run `make docker` to build a docker image local
### The Managarr CLI
Managarr can be used in one of two ways: As a TUI, or as a CLI for managing your Servarrs.
All management features available in the TUI are also available in the CLI.
All management features available in the TUI are also available in the CLI. However, the CLI is
equipped with additional features to allow for more advanced usage and automation.
The CLI can be helpful for automating tasks or for use in scripts. For example, you can use the CLI to trigger a search for a movie, or to add a movie to your library.
@@ -111,12 +113,12 @@ To see all available commands, simply run `managarr --help`:
```shell
$ managarr --help
managarr 0.0.36
managarr 0.1.3
Alex Clarke <alex.j.tusa@gmail.com>
A TUI and CLI to manage your Servarrs
Usage: managarr [COMMAND]
Usage: managarr [OPTIONS] [COMMAND]
Commands:
radarr Commands for manging your Radarr instance
@@ -124,6 +126,8 @@ Commands:
help Print this message or the help of the given subcommand(s)
Options:
--config <CONFIG> The Managarr configuration file to use
--disable-terminal-size-checks Disable the terminal size checks
-h, --help Print help
-V, --version Print version
```
@@ -134,7 +138,7 @@ All subcommands also have detailed help menus to show you how to use them. For e
$ managarr radarr --help
Commands for manging your Radarr instance
Usage: managarr radarr <COMMAND>
Usage: managarr radarr [OPTIONS] <COMMAND>
Commands:
add Commands to add or create new resources within your Radarr instance
@@ -154,6 +158,8 @@ Commands:
help Print this message or the help of the given subcommand(s)
Options:
--config <CONFIG> The Managarr configuration file to use
--disable-terminal-size-checks Disable the terminal size checks
-h, --help Print help
```
@@ -185,6 +191,15 @@ $HOME/Library/Application Support/managarr/config.yml
%APPDATA%/Roaming/managarr/config.yml
```
## Specify Which Configuration File to Use
It can sometimes be useful to specify the configuration file you wish to use. This is useful in cases
where you may have more than one instance of a given Servarr running. Thus, you can specify the
config file using the `--config` flag:
```shell
managarr --config /path/to/config.yml
```
### Example Configuration:
```yaml
radarr:
+10
View File
@@ -21,6 +21,8 @@ mod get_command_handler_tests;
pub enum RadarrGetCommand {
#[command(about = "Get the shared settings for all indexers")]
AllIndexerSettings,
#[command(about = "Fetch the host config for your Radarr instance")]
HostConfig,
#[command(about = "Get detailed information for the movie with the given ID")]
MovieDetails {
#[arg(
@@ -39,6 +41,8 @@ pub enum RadarrGetCommand {
)]
movie_id: i64,
},
#[command(about = "Fetch the security config for your Radarr instance")]
SecurityConfig,
#[command(about = "Get the system status")]
SystemStatus,
}
@@ -73,12 +77,18 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrGetCommand> for RadarrGetCommandHan
RadarrGetCommand::AllIndexerSettings => {
execute_network_event!(self, RadarrEvent::GetAllIndexerSettings);
}
RadarrGetCommand::HostConfig => {
execute_network_event!(self, RadarrEvent::GetHostConfig);
}
RadarrGetCommand::MovieDetails { movie_id } => {
execute_network_event!(self, RadarrEvent::GetMovieDetails(Some(movie_id)));
}
RadarrGetCommand::MovieHistory { movie_id } => {
execute_network_event!(self, RadarrEvent::GetMovieHistory(Some(movie_id)));
}
RadarrGetCommand::SecurityConfig => {
execute_network_event!(self, RadarrEvent::GetSecurityConfig);
}
RadarrGetCommand::SystemStatus => {
execute_network_event!(self, RadarrEvent::GetStatus);
}
@@ -29,6 +29,14 @@ mod test {
assert!(result.is_ok());
}
#[test]
fn test_get_host_config_has_no_arg_requirements() {
let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "host-config"]);
assert!(result.is_ok());
}
#[test]
fn test_movie_details_requires_movie_id() {
let result =
@@ -81,6 +89,14 @@ mod test {
assert!(result.is_ok());
}
#[test]
fn test_get_security_config_has_no_arg_requirements() {
let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "security-config"]);
assert!(result.is_ok());
}
#[test]
fn test_system_status_has_no_arg_requirements() {
let result =
@@ -135,6 +151,29 @@ mod test {
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_get_host_config_command() {
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(RadarrEvent::GetHostConfig.into()))
.times(1)
.returning(|_| {
Ok(Serdeable::Radarr(RadarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::default()));
let get_host_config_command = RadarrGetCommand::HostConfig;
let result =
RadarrGetCommandHandler::with(&app_arc, get_host_config_command, &mut mock_network)
.handle()
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_get_movie_details_command() {
let expected_movie_id = 1;
@@ -187,6 +226,29 @@ mod test {
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_get_security_config_command() {
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(RadarrEvent::GetSecurityConfig.into()))
.times(1)
.returning(|_| {
Ok(Serdeable::Radarr(RadarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::default()));
let get_security_config_command = RadarrGetCommand::SecurityConfig;
let result =
RadarrGetCommandHandler::with(&app_arc, get_security_config_command, &mut mock_network)
.handle()
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_get_system_status_command() {
let mut mock_network = MockNetworkTrait::new();
+29 -3
View File
@@ -1,12 +1,16 @@
#![warn(rust_2018_idioms)]
use std::fs::File;
use std::io::BufReader;
use std::panic::PanicHookInfo;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::{io, panic, process};
use anyhow::anyhow;
use anyhow::Result;
use app::AppConfig;
use clap::{
command, crate_authors, crate_description, crate_name, crate_version, CommandFactory, Parser,
};
@@ -62,6 +66,22 @@ static MIN_TERM_HEIGHT: u16 = 40;
struct Cli {
#[command(subcommand)]
command: Option<Command>,
#[arg(
long,
global = true,
value_parser,
help = "The Managarr configuration file to use"
)]
config: Option<PathBuf>,
#[arg(long, global = true, help = "Disable the terminal size checks")]
disable_terminal_size_checks: bool,
}
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)
}
#[tokio::main]
@@ -73,7 +93,11 @@ async fn main() -> Result<()> {
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
let args = Cli::parse();
let config = confy::load("managarr", "config")?;
let config = if let Some(ref config_file) = args.config {
load_config(config_file.to_str().expect("Invalid config file specified"))?
} else {
confy::load("managarr", "config")?
};
let (sync_network_tx, sync_network_rx) = mpsc::channel(500);
let cancellation_token = CancellationToken::new();
let ctrlc_cancellation_token = cancellation_token.clone();
@@ -110,7 +134,7 @@ async fn main() -> Result<()> {
None => {
let app_nw = Arc::clone(&app);
std::thread::spawn(move || start_networking(sync_network_rx, &app_nw, cancellation_token));
start_ui(&app).await?;
start_ui(&app, !args.disable_terminal_size_checks).await?;
}
}
@@ -132,7 +156,8 @@ async fn start_networking(
}
}
async fn start_ui(app: &Arc<Mutex<App<'_>>>) -> Result<()> {
async fn start_ui(app: &Arc<Mutex<App<'_>>>, check_terminal_size: bool) -> Result<()> {
if check_terminal_size {
let (width, height) = size()?;
if width < MIN_TERM_WIDTH || height < MIN_TERM_HEIGHT {
return Err(anyhow!(
@@ -143,6 +168,7 @@ async fn start_ui(app: &Arc<Mutex<App<'_>>>) -> Result<()> {
height
));
}
}
let mut stdout = io::stdout();
enable_raw_mode()?;
+85
View File
@@ -57,6 +57,44 @@ pub struct AddRootFolderBody {
pub path: String,
}
#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, ValueEnum)]
#[serde(rename_all = "camelCase")]
pub enum AuthenticationMethod {
#[default]
Basic,
Forms,
None,
}
impl Display for AuthenticationMethod {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let authentication_method = match self {
AuthenticationMethod::Basic => "basic",
AuthenticationMethod::Forms => "forms",
AuthenticationMethod::None => "none",
};
write!(f, "{authentication_method}")
}
}
#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, ValueEnum)]
#[serde(rename_all = "camelCase")]
pub enum AuthenticationRequired {
Enabled,
#[default]
DisabledForLocalAddresses,
}
impl Display for AuthenticationRequired {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let authentication_required = match self {
AuthenticationRequired::Enabled => "enabled",
AuthenticationRequired::DisabledForLocalAddresses => "disabledForLocalAddresses",
};
write!(f, "{authentication_required}")
}
}
#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct BlocklistResponse {
pub records: Vec<BlocklistItem>,
@@ -85,6 +123,26 @@ pub struct BlocklistItemMovie {
pub title: HorizontallyScrollableText,
}
#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, ValueEnum)]
#[serde(rename_all = "camelCase")]
pub enum CertificateValidation {
#[default]
Enabled,
DisabledForLocalAddresses,
Disabled,
}
impl Display for CertificateValidation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let certificate_validation = match self {
CertificateValidation::Enabled => "enabled",
CertificateValidation::DisabledForLocalAddresses => "disabledForLocalAddresses",
CertificateValidation::Disabled => "disabled",
};
write!(f, "{certificate_validation}")
}
}
#[derive(Serialize, Deserialize, Derivative, Default, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Collection {
@@ -223,6 +281,18 @@ pub struct EditMovieParams {
pub clear_tags: bool,
}
#[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct HostConfig {
pub bind_address: HorizontallyScrollableText,
#[serde(deserialize_with = "super::from_i64")]
pub port: i64,
pub url_base: Option<HorizontallyScrollableText>,
pub instance_name: Option<HorizontallyScrollableText>,
pub application_url: Option<HorizontallyScrollableText>,
pub enable_ssl: bool,
}
#[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Indexer {
@@ -560,6 +630,17 @@ pub struct RootFolder {
pub unmapped_folders: Option<Vec<UnmappedFolder>>,
}
#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SecurityConfig {
pub authentication_method: AuthenticationMethod,
pub authentication_required: AuthenticationRequired,
pub username: String,
pub password: Option<String>,
pub api_key: String,
pub certificate_validation: CertificateValidation,
}
#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SystemStatus {
@@ -647,6 +728,7 @@ pub enum RadarrSerdeable {
Credits(Vec<Credit>),
DiskSpaces(Vec<DiskSpace>),
DownloadsResponse(DownloadsResponse),
HostConfig(HostConfig),
Indexers(Vec<Indexer>),
IndexerSettings(IndexerSettings),
LogResponse(LogResponse),
@@ -657,6 +739,7 @@ pub enum RadarrSerdeable {
QueueEvents(Vec<QueueEvent>),
Releases(Vec<Release>),
RootFolders(Vec<RootFolder>),
SecurityConfig(SecurityConfig),
SystemStatus(SystemStatus),
Tags(Vec<Tag>),
Tasks(Vec<Task>),
@@ -686,6 +769,7 @@ serde_enum_from!(
Credits(Vec<Credit>),
DiskSpaces(Vec<DiskSpace>),
DownloadsResponse(DownloadsResponse),
HostConfig(HostConfig),
Indexers(Vec<Indexer>),
IndexerSettings(IndexerSettings),
LogResponse(LogResponse),
@@ -696,6 +780,7 @@ serde_enum_from!(
QueueEvents(Vec<QueueEvent>),
Releases(Vec<Release>),
RootFolders(Vec<RootFolder>),
SecurityConfig(SecurityConfig),
SystemStatus(SystemStatus),
Tags(Vec<Tag>),
Tasks(Vec<Task>),
+31 -4
View File
@@ -5,14 +5,41 @@ mod tests {
use crate::models::{
radarr_models::{
AddMovieSearchResult, BlocklistItem, BlocklistResponse, Collection, Credit, DiskSpace,
DownloadRecord, DownloadsResponse, Indexer, IndexerSettings, IndexerTestResult, Log,
LogResponse, MinimumAvailability, Monitor, Movie, MovieHistoryItem, QualityProfile,
QueueEvent, RadarrSerdeable, Release, RootFolder, SystemStatus, Tag, Task, TaskName, Update,
AddMovieSearchResult, AuthenticationMethod, AuthenticationRequired, BlocklistItem,
BlocklistResponse, CertificateValidation, Collection, Credit, DiskSpace, DownloadRecord,
DownloadsResponse, Indexer, IndexerSettings, IndexerTestResult, Log, LogResponse,
MinimumAvailability, Monitor, Movie, MovieHistoryItem, QualityProfile, QueueEvent,
RadarrSerdeable, Release, RootFolder, SystemStatus, Tag, Task, TaskName, Update,
},
Serdeable,
};
#[test]
fn test_authentication_method_display() {
assert_str_eq!(AuthenticationMethod::Basic.to_string(), "basic");
assert_str_eq!(AuthenticationMethod::Forms.to_string(), "forms");
assert_str_eq!(AuthenticationMethod::None.to_string(), "none");
}
#[test]
fn test_authentication_required_display() {
assert_str_eq!(AuthenticationRequired::Enabled.to_string(), "enabled");
assert_str_eq!(
AuthenticationRequired::DisabledForLocalAddresses.to_string(),
"disabledForLocalAddresses"
);
}
#[test]
fn test_certificate_validation_display() {
assert_str_eq!(CertificateValidation::Enabled.to_string(), "enabled");
assert_str_eq!(
CertificateValidation::DisabledForLocalAddresses.to_string(),
"disabledForLocalAddresses"
);
assert_str_eq!(CertificateValidation::Disabled.to_string(), "disabled");
}
#[test]
fn test_task_name_display() {
assert_str_eq!(
+43 -6
View File
@@ -11,10 +11,10 @@ use crate::app::RadarrConfig;
use crate::models::radarr_models::{
AddMovieBody, AddMovieSearchResult, AddOptions, AddRootFolderBody, BlocklistResponse, Collection,
CollectionMovie, CommandBody, Credit, CreditType, DeleteMovieParams, DiskSpace, DownloadRecord,
DownloadsResponse, EditCollectionParams, EditIndexerParams, EditMovieParams, Indexer,
DownloadsResponse, EditCollectionParams, EditIndexerParams, EditMovieParams, HostConfig, Indexer,
IndexerSettings, IndexerTestResult, LogResponse, Movie, MovieCommandBody, MovieHistoryItem,
QualityProfile, QueueEvent, RadarrSerdeable, Release, ReleaseDownloadBody, RootFolder,
SystemStatus, Tag, Task, TaskName, Update,
SecurityConfig, SystemStatus, Tag, Task, TaskName, Update,
};
use crate::models::servarr_data::radarr::modals::{
AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem,
@@ -50,6 +50,7 @@ pub enum RadarrEvent {
GetBlocklist,
GetCollections,
GetDownloads,
GetHostConfig,
GetIndexers,
GetAllIndexerSettings,
GetLogs(Option<u64>),
@@ -62,6 +63,7 @@ pub enum RadarrEvent {
GetQueuedEvents,
GetReleases(Option<i64>),
GetRootFolders,
GetSecurityConfig,
GetStatus,
GetTags,
GetTasks,
@@ -86,6 +88,7 @@ impl RadarrEvent {
RadarrEvent::GetBlocklist => "/blocklist?page=1&pageSize=10000",
RadarrEvent::GetCollections | RadarrEvent::EditCollection(_) => "/collection",
RadarrEvent::GetDownloads | RadarrEvent::DeleteDownload(_) => "/queue",
RadarrEvent::GetHostConfig | RadarrEvent::GetSecurityConfig => "/config/host",
RadarrEvent::GetIndexers | RadarrEvent::EditIndexer(_) | RadarrEvent::DeleteIndexer(_) => {
"/indexer"
}
@@ -179,14 +182,15 @@ impl<'a, 'b> Network<'a, 'b> {
self.edit_indexer(params).await.map(RadarrSerdeable::from)
}
RadarrEvent::EditMovie(params) => self.edit_movie(params).await.map(RadarrSerdeable::from),
RadarrEvent::GetBlocklist => self.get_blocklist().await.map(RadarrSerdeable::from),
RadarrEvent::GetCollections => self.get_collections().await.map(RadarrSerdeable::from),
RadarrEvent::GetDownloads => self.get_downloads().await.map(RadarrSerdeable::from),
RadarrEvent::GetIndexers => self.get_indexers().await.map(RadarrSerdeable::from),
RadarrEvent::GetAllIndexerSettings => self
.get_all_indexer_settings()
.await
.map(RadarrSerdeable::from),
RadarrEvent::GetBlocklist => self.get_blocklist().await.map(RadarrSerdeable::from),
RadarrEvent::GetCollections => self.get_collections().await.map(RadarrSerdeable::from),
RadarrEvent::GetDownloads => self.get_downloads().await.map(RadarrSerdeable::from),
RadarrEvent::GetHostConfig => self.get_host_config().await.map(RadarrSerdeable::from),
RadarrEvent::GetIndexers => self.get_indexers().await.map(RadarrSerdeable::from),
RadarrEvent::GetLogs(events) => self.get_logs(events).await.map(RadarrSerdeable::from),
RadarrEvent::GetMovieCredits(movie_id) => {
self.get_credits(movie_id).await.map(RadarrSerdeable::from)
@@ -209,6 +213,7 @@ impl<'a, 'b> Network<'a, 'b> {
self.get_releases(movie_id).await.map(RadarrSerdeable::from)
}
RadarrEvent::GetRootFolders => self.get_root_folders().await.map(RadarrSerdeable::from),
RadarrEvent::GetSecurityConfig => self.get_security_config().await.map(RadarrSerdeable::from),
RadarrEvent::GetStatus => self.get_status().await.map(RadarrSerdeable::from),
RadarrEvent::GetTags => self.get_tags().await.map(RadarrSerdeable::from),
RadarrEvent::GetTasks => self.get_tasks().await.map(RadarrSerdeable::from),
@@ -1354,6 +1359,22 @@ impl<'a, 'b> Network<'a, 'b> {
.await
}
async fn get_host_config(&mut self) -> Result<HostConfig> {
info!("Fetching Radarr host config");
let request_props = self
.radarr_request_props_from(
RadarrEvent::GetHostConfig.resource(),
RequestMethod::Get,
None::<()>,
)
.await;
self
.handle_request::<(), HostConfig>(request_props, |_, _| ())
.await
}
async fn get_indexers(&mut self) -> Result<Vec<Indexer>> {
info!("Fetching Radarr indexers");
@@ -1765,6 +1786,22 @@ impl<'a, 'b> Network<'a, 'b> {
.await
}
async fn get_security_config(&mut self) -> Result<SecurityConfig> {
info!("Fetching Radarr security config");
let request_props = self
.radarr_request_props_from(
RadarrEvent::GetSecurityConfig.resource(),
RequestMethod::Get,
None::<()>,
)
.await;
self
.handle_request::<(), SecurityConfig>(request_props, |_, _| ())
.await
}
async fn get_status(&mut self) -> Result<SystemStatus> {
info!("Fetching Radarr system status");
+71 -1
View File
@@ -174,6 +174,13 @@ mod test {
assert_str_eq!(event.resource(), "/queue");
}
#[rstest]
fn test_resource_host_config(
#[values(RadarrEvent::GetHostConfig, RadarrEvent::GetSecurityConfig)] event: RadarrEvent,
) {
assert_str_eq!(event.resource(), "/config/host");
}
#[rstest]
fn test_resource_command(
#[values(
@@ -2171,6 +2178,37 @@ mod test {
}
}
#[tokio::test]
async fn test_handle_get_host_config_event() {
let host_config_response = json!({
"bindAddress": "*",
"port": 7878,
"urlBase": "some.test.site/radarr",
"instanceName": "Radarr",
"applicationUrl": "https://some.test.site:7878/radarr",
"enableSsl": true
});
let response: HostConfig = serde_json::from_value(host_config_response.clone()).unwrap();
let (async_server, app_arc, _server) = mock_radarr_api(
RequestMethod::Get,
None,
Some(host_config_response),
None,
RadarrEvent::GetHostConfig.resource(),
)
.await;
let mut network = Network::new(&app_arc, CancellationToken::new());
if let RadarrSerdeable::HostConfig(host_config) = network
.handle_radarr_event(RadarrEvent::GetHostConfig)
.await
.unwrap()
{
async_server.assert_async().await;
assert_eq!(host_config, response);
}
}
#[tokio::test]
async fn test_handle_get_indexers_event() {
let indexers_response_json = json!([{
@@ -2711,7 +2749,7 @@ mod test {
}
#[tokio::test]
async fn test_add_tag() {
async fn test_handle_add_tag() {
let tag_json = json!({ "id": 3, "label": "testing" });
let response: Tag = serde_json::from_value(tag_json.clone()).unwrap();
let (async_server, app_arc, _server) = mock_radarr_api(
@@ -2792,6 +2830,38 @@ mod test {
}
}
#[tokio::test]
async fn test_handle_get_security_config_event() {
let security_config_response = json!({
"authenticationMethod": "forms",
"authenticationRequired": "disabledForLocalAddresses",
"username": "test",
"password": "some password",
"apiKey": "someApiKey12345",
"certificateValidation": "disabledForLocalAddresses",
});
let response: SecurityConfig =
serde_json::from_value(security_config_response.clone()).unwrap();
let (async_server, app_arc, _server) = mock_radarr_api(
RequestMethod::Get,
None,
Some(security_config_response),
None,
RadarrEvent::GetSecurityConfig.resource(),
)
.await;
let mut network = Network::new(&app_arc, CancellationToken::new());
if let RadarrSerdeable::SecurityConfig(security_config) = network
.handle_radarr_event(RadarrEvent::GetSecurityConfig)
.await
.unwrap()
{
async_server.assert_async().await;
assert_eq!(security_config, response);
}
}
#[tokio::test]
async fn test_handle_get_movie_credits_event() {
let credits_json = json!([