feat: Lidarr CLI support for downloading a release
This commit is contained in:
@@ -60,6 +60,55 @@ mod tests {
|
|||||||
assert_err!(&result);
|
assert_err!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_download_release_requires_guid() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"download-release",
|
||||||
|
"--indexer-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_download_release_requires_indexer_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"download-release",
|
||||||
|
"--guid",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_download_release_requirements_satisfied() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"download-release",
|
||||||
|
"--guid",
|
||||||
|
"1",
|
||||||
|
"--indexer-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_toggle_artist_monitoring_requires_artist_id() {
|
fn test_toggle_artist_monitoring_requires_artist_id() {
|
||||||
let result =
|
let result =
|
||||||
@@ -235,7 +284,7 @@ mod tests {
|
|||||||
use crate::cli::lidarr::manual_search_command_handler::LidarrManualSearchCommand;
|
use crate::cli::lidarr::manual_search_command_handler::LidarrManualSearchCommand;
|
||||||
use crate::cli::lidarr::refresh_command_handler::LidarrRefreshCommand;
|
use crate::cli::lidarr::refresh_command_handler::LidarrRefreshCommand;
|
||||||
use crate::cli::lidarr::trigger_automatic_search_command_handler::LidarrTriggerAutomaticSearchCommand;
|
use crate::cli::lidarr::trigger_automatic_search_command_handler::LidarrTriggerAutomaticSearchCommand;
|
||||||
use crate::models::lidarr_models::LidarrTaskName;
|
use crate::models::lidarr_models::{LidarrReleaseDownloadBody, LidarrTaskName};
|
||||||
use crate::models::servarr_models::IndexerSettings;
|
use crate::models::servarr_models::IndexerSettings;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
@@ -428,9 +477,9 @@ mod tests {
|
|||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let refresh_series_command = LidarrCommand::Refresh(LidarrRefreshCommand::AllArtists);
|
let refresh_artist_command = LidarrCommand::Refresh(LidarrRefreshCommand::AllArtists);
|
||||||
|
|
||||||
let result = LidarrCliHandler::with(&app_arc, refresh_series_command, &mut mock_network)
|
let result = LidarrCliHandler::with(&app_arc, refresh_artist_command, &mut mock_network)
|
||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -497,6 +546,37 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_download_release_command() {
|
||||||
|
let expected_release_download_body = LidarrReleaseDownloadBody {
|
||||||
|
guid: "guid".to_owned(),
|
||||||
|
indexer_id: 1,
|
||||||
|
};
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::DownloadRelease(expected_release_download_body).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let download_release_command = LidarrCommand::DownloadRelease {
|
||||||
|
guid: "guid".to_owned(),
|
||||||
|
indexer_id: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LidarrCliHandler::with(&app_arc, download_release_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_toggle_artist_monitoring_command() {
|
async fn test_toggle_artist_monitoring_command() {
|
||||||
let mut mock_network = MockNetworkTrait::new();
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::cli::lidarr::LidarrCommand;
|
use crate::cli::lidarr::LidarrCommand;
|
||||||
use crate::cli::{CliCommandHandler, Command};
|
use crate::cli::{CliCommandHandler, Command};
|
||||||
|
use crate::models::Serdeable;
|
||||||
|
use crate::models::lidarr_models::{LidarrRelease, LidarrSerdeable};
|
||||||
use crate::network::NetworkTrait;
|
use crate::network::NetworkTrait;
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
|
use serde_json::json;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
@@ -15,7 +18,7 @@ mod manual_search_command_handler_tests;
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
pub enum LidarrManualSearchCommand {
|
pub enum LidarrManualSearchCommand {
|
||||||
#[command(
|
#[command(
|
||||||
about = "Trigger a manual search of discography releases for the given artist corresponding to the artist with the given ID.\nNote that when downloading a discography release, ensure that the release includes 'discography: true', otherwise you'll run into issues"
|
about = "Trigger a manual search of discography releases for the given artist corresponding to the artist with the given ID."
|
||||||
)]
|
)]
|
||||||
Discography {
|
Discography {
|
||||||
#[arg(
|
#[arg(
|
||||||
@@ -58,11 +61,21 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrManualSearchCommand>
|
|||||||
let result = match self.command {
|
let result = match self.command {
|
||||||
LidarrManualSearchCommand::Discography { artist_id } => {
|
LidarrManualSearchCommand::Discography { artist_id } => {
|
||||||
println!("Searching for artist discography releases. This may take a minute...");
|
println!("Searching for artist discography releases. This may take a minute...");
|
||||||
let resp = self
|
match self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(LidarrEvent::GetDiscographyReleases(artist_id).into())
|
.handle_network_event(LidarrEvent::GetDiscographyReleases(artist_id).into())
|
||||||
.await?;
|
.await
|
||||||
serde_json::to_string_pretty(&resp)?
|
{
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Releases(releases_vec))) => {
|
||||||
|
let discography_vec: Vec<LidarrRelease> = releases_vec
|
||||||
|
.into_iter()
|
||||||
|
.filter(|release| release.discography)
|
||||||
|
.collect();
|
||||||
|
serde_json::to_string_pretty(&discography_vec)?
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
_ => serde_json::to_string_pretty(&json!({"message": "Failed to parse response"}))?,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+21
-2
@@ -18,7 +18,7 @@ use super::{CliCommandHandler, Command};
|
|||||||
use crate::cli::lidarr::manual_search_command_handler::{
|
use crate::cli::lidarr::manual_search_command_handler::{
|
||||||
LidarrManualSearchCommand, LidarrManualSearchCommandHandler,
|
LidarrManualSearchCommand, LidarrManualSearchCommandHandler,
|
||||||
};
|
};
|
||||||
use crate::models::lidarr_models::LidarrTaskName;
|
use crate::models::lidarr_models::{LidarrReleaseDownloadBody, LidarrTaskName};
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
use crate::{app::App, network::NetworkTrait};
|
use crate::{app::App, network::NetworkTrait};
|
||||||
|
|
||||||
@@ -27,13 +27,13 @@ mod delete_command_handler;
|
|||||||
mod edit_command_handler;
|
mod edit_command_handler;
|
||||||
mod get_command_handler;
|
mod get_command_handler;
|
||||||
mod list_command_handler;
|
mod list_command_handler;
|
||||||
|
mod manual_search_command_handler;
|
||||||
mod refresh_command_handler;
|
mod refresh_command_handler;
|
||||||
mod trigger_automatic_search_command_handler;
|
mod trigger_automatic_search_command_handler;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "lidarr_command_tests.rs"]
|
#[path = "lidarr_command_tests.rs"]
|
||||||
mod lidarr_command_tests;
|
mod lidarr_command_tests;
|
||||||
mod manual_search_command_handler;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
pub enum LidarrCommand {
|
pub enum LidarrCommand {
|
||||||
@@ -74,6 +74,17 @@ pub enum LidarrCommand {
|
|||||||
about = "Commands to trigger automatic searches for releases of different resources in your Lidarr instance"
|
about = "Commands to trigger automatic searches for releases of different resources in your Lidarr instance"
|
||||||
)]
|
)]
|
||||||
TriggerAutomaticSearch(LidarrTriggerAutomaticSearchCommand),
|
TriggerAutomaticSearch(LidarrTriggerAutomaticSearchCommand),
|
||||||
|
#[command(about = "Manually download the given release")]
|
||||||
|
DownloadRelease {
|
||||||
|
#[arg(long, help = "The GUID of the release to download", required = true)]
|
||||||
|
guid: String,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The indexer ID to download the release from",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
indexer_id: i64,
|
||||||
|
},
|
||||||
#[command(about = "Mark the Lidarr history item with the given ID as 'failed'")]
|
#[command(about = "Mark the Lidarr history item with the given ID as 'failed'")]
|
||||||
MarkHistoryItemAsFailed {
|
MarkHistoryItemAsFailed {
|
||||||
#[arg(
|
#[arg(
|
||||||
@@ -206,6 +217,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, '
|
|||||||
.handle()
|
.handle()
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
LidarrCommand::DownloadRelease { guid, indexer_id } => {
|
||||||
|
let params = LidarrReleaseDownloadBody { guid, indexer_id };
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::DownloadRelease(params).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
LidarrCommand::MarkHistoryItemAsFailed { history_item_id } => {
|
LidarrCommand::MarkHistoryItemAsFailed { history_item_id } => {
|
||||||
let _ = self
|
let _ = self
|
||||||
.network
|
.network
|
||||||
|
|||||||
@@ -488,6 +488,13 @@ pub struct LidarrRelease {
|
|||||||
pub quality: QualityWrapper,
|
pub quality: QualityWrapper,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Serialize, Debug, PartialEq, Eq, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct LidarrReleaseDownloadBody {
|
||||||
|
pub guid: String,
|
||||||
|
pub indexer_id: i64,
|
||||||
|
}
|
||||||
|
|
||||||
impl From<LidarrSerdeable> for Serdeable {
|
impl From<LidarrSerdeable> for Serdeable {
|
||||||
fn from(value: LidarrSerdeable) -> Serdeable {
|
fn from(value: LidarrSerdeable) -> Serdeable {
|
||||||
Serdeable::Lidarr(value)
|
Serdeable::Lidarr(value)
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::models::lidarr_models::LidarrReleaseDownloadBody;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::network_tests::test_utils::{MockServarrApi, test_network};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_download_lidarr_release_event_uses_provided_params() {
|
||||||
|
let params = LidarrReleaseDownloadBody {
|
||||||
|
guid: "1234".to_owned(),
|
||||||
|
indexer_id: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (mock, app, _server) = MockServarrApi::post()
|
||||||
|
.with_request_body(json!({
|
||||||
|
"guid": "1234",
|
||||||
|
"indexerId": 2,
|
||||||
|
}))
|
||||||
|
.returns(json!({}))
|
||||||
|
.build_for(LidarrEvent::DownloadRelease(params.clone()))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
app.lock().await.server_tabs.set_index(2);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
let result = network
|
||||||
|
.handle_lidarr_event(LidarrEvent::DownloadRelease(params))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
mock.assert_async().await;
|
||||||
|
assert_ok!(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,37 @@
|
|||||||
|
use crate::models::lidarr_models::LidarrReleaseDownloadBody;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::{Network, RequestMethod};
|
||||||
|
use anyhow::Result;
|
||||||
|
use log::info;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
mod albums;
|
mod albums;
|
||||||
mod artists;
|
mod artists;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "lidarr_library_network_tests.rs"]
|
||||||
|
mod lidarr_library_network_tests;
|
||||||
|
|
||||||
|
impl Network<'_, '_> {
|
||||||
|
pub(in crate::network::lidarr_network) async fn download_lidarr_release(
|
||||||
|
&mut self,
|
||||||
|
lidarr_release_download_body: LidarrReleaseDownloadBody,
|
||||||
|
) -> Result<Value> {
|
||||||
|
let event = LidarrEvent::DownloadRelease(LidarrReleaseDownloadBody::default());
|
||||||
|
info!("Downloading Lidarr release with params: {lidarr_release_download_body:?}");
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
event,
|
||||||
|
RequestMethod::Post,
|
||||||
|
Some(lidarr_release_download_body),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<LidarrReleaseDownloadBody, Value>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -125,7 +125,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_resource_release(#[values(LidarrEvent::GetDiscographyReleases(0))] event: LidarrEvent) {
|
fn test_resource_release(
|
||||||
|
#[values(
|
||||||
|
LidarrEvent::GetDiscographyReleases(0),
|
||||||
|
LidarrEvent::DownloadRelease(Default::default())
|
||||||
|
)]
|
||||||
|
event: LidarrEvent,
|
||||||
|
) {
|
||||||
assert_str_eq!(event.resource(), "/release");
|
assert_str_eq!(event.resource(), "/release");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use log::info;
|
|||||||
|
|
||||||
use super::{NetworkEvent, NetworkResource};
|
use super::{NetworkEvent, NetworkResource};
|
||||||
use crate::models::lidarr_models::{
|
use crate::models::lidarr_models::{
|
||||||
AddArtistBody, AddLidarrRootFolderBody, DeleteParams, EditArtistParams, LidarrSerdeable,
|
AddArtistBody, AddLidarrRootFolderBody, DeleteParams, EditArtistParams,
|
||||||
LidarrTaskName, MetadataProfile,
|
LidarrReleaseDownloadBody, LidarrSerdeable, LidarrTaskName, MetadataProfile,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_models::{EditIndexerParams, IndexerSettings, QualityProfile, Tag};
|
use crate::models::servarr_models::{EditIndexerParams, IndexerSettings, QualityProfile, Tag};
|
||||||
use crate::network::{Network, RequestMethod};
|
use crate::network::{Network, RequestMethod};
|
||||||
@@ -35,6 +35,7 @@ pub enum LidarrEvent {
|
|||||||
DeleteIndexer(i64),
|
DeleteIndexer(i64),
|
||||||
DeleteRootFolder(i64),
|
DeleteRootFolder(i64),
|
||||||
DeleteTag(i64),
|
DeleteTag(i64),
|
||||||
|
DownloadRelease(LidarrReleaseDownloadBody),
|
||||||
EditArtist(EditArtistParams),
|
EditArtist(EditArtistParams),
|
||||||
EditAllIndexerSettings(IndexerSettings),
|
EditAllIndexerSettings(IndexerSettings),
|
||||||
EditIndexer(EditIndexerParams),
|
EditIndexer(EditIndexerParams),
|
||||||
@@ -97,7 +98,7 @@ impl NetworkResource for LidarrEvent {
|
|||||||
LidarrEvent::GetDownloads(_) | LidarrEvent::DeleteDownload(_) => "/queue",
|
LidarrEvent::GetDownloads(_) | LidarrEvent::DeleteDownload(_) => "/queue",
|
||||||
LidarrEvent::GetHistory(_) => "/history",
|
LidarrEvent::GetHistory(_) => "/history",
|
||||||
LidarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed",
|
LidarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed",
|
||||||
LidarrEvent::GetDiscographyReleases(_) => "/release",
|
LidarrEvent::GetDiscographyReleases(_) | LidarrEvent::DownloadRelease(_) => "/release",
|
||||||
LidarrEvent::GetHostConfig | LidarrEvent::GetSecurityConfig => "/config/host",
|
LidarrEvent::GetHostConfig | LidarrEvent::GetSecurityConfig => "/config/host",
|
||||||
LidarrEvent::GetIndexers | LidarrEvent::DeleteIndexer(_) | LidarrEvent::EditIndexer(_) => {
|
LidarrEvent::GetIndexers | LidarrEvent::DeleteIndexer(_) | LidarrEvent::EditIndexer(_) => {
|
||||||
"/indexer"
|
"/indexer"
|
||||||
@@ -171,6 +172,10 @@ impl Network<'_, '_> {
|
|||||||
.delete_lidarr_tag(tag_id)
|
.delete_lidarr_tag(tag_id)
|
||||||
.await
|
.await
|
||||||
.map(LidarrSerdeable::from),
|
.map(LidarrSerdeable::from),
|
||||||
|
LidarrEvent::DownloadRelease(lidarr_release_download_body) => self
|
||||||
|
.download_lidarr_release(lidarr_release_download_body)
|
||||||
|
.await
|
||||||
|
.map(LidarrSerdeable::from),
|
||||||
LidarrEvent::GetAlbums(artist_id) => {
|
LidarrEvent::GetAlbums(artist_id) => {
|
||||||
self.get_albums(artist_id).await.map(LidarrSerdeable::from)
|
self.get_albums(artist_id).await.map(LidarrSerdeable::from)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user