Compare commits
5 Commits
eff1a901eb
...
lidarr
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e96e74c87 | |||
| ddb869c341 | |||
| f17f542e8e | |||
| a2e6400a38 | |||
| 89f5ff6bc7 |
@@ -12,17 +12,16 @@
|
|||||||

|

|
||||||
[](https://matrix.to/#/#managarr:matrix.org)
|
[](https://matrix.to/#/#managarr:matrix.org)
|
||||||
|
|
||||||
|
|
||||||
Managarr is a TUI and CLI to help you manage your HTPC (Home Theater PC). Built with 🤎 in Rust!
|
Managarr is a TUI and CLI to help you manage your HTPC (Home Theater PC). Built with 🤎 in Rust!
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## What Servarrs are supported?
|
## What Servarrs are supported?
|
||||||
|
|
||||||
- [x]  [Radarr](https://wiki.servarr.com/radarr)
|
- [x]  [Radarr](https://wiki.servarr.com/radarr)
|
||||||
- [x]  [Sonarr](https://wiki.servarr.com/en/sonarr)
|
- [x]  [Sonarr](https://wiki.servarr.com/en/sonarr)
|
||||||
|
- [x]  [Lidarr](https://wiki.servarr.com/en/lidarr)
|
||||||
- [ ]  [Readarr](https://wiki.servarr.com/en/readarr)
|
- [ ]  [Readarr](https://wiki.servarr.com/en/readarr)
|
||||||
- [ ]  [Lidarr](https://wiki.servarr.com/en/lidarr)
|
|
||||||
- [ ]  [Prowlarr](https://wiki.servarr.com/en/prowlarr)
|
- [ ]  [Prowlarr](https://wiki.servarr.com/en/prowlarr)
|
||||||
- [ ]  [Whisparr](https://wiki.servarr.com/whisparr)
|
- [ ]  [Whisparr](https://wiki.servarr.com/whisparr)
|
||||||
- [ ]  [Bazarr](https://www.bazarr.media/)
|
- [ ]  [Bazarr](https://www.bazarr.media/)
|
||||||
@@ -96,7 +95,7 @@ of Chocolatey packages take quite some time, and thus the package may not be ava
|
|||||||
choco install managarr
|
choco install managarr
|
||||||
|
|
||||||
# Some newer releases may require a version number, so you can specify it like so:
|
# Some newer releases may require a version number, so you can specify it like so:
|
||||||
choco install managarr --version=0.5.0
|
choco install managarr --version=0.7.0
|
||||||
```
|
```
|
||||||
|
|
||||||
To upgrade to the latest and greatest version of Managarr:
|
To upgrade to the latest and greatest version of Managarr:
|
||||||
@@ -104,7 +103,7 @@ To upgrade to the latest and greatest version of Managarr:
|
|||||||
choco upgrade managarr
|
choco upgrade managarr
|
||||||
|
|
||||||
# To upgrade to a specific version:
|
# To upgrade to a specific version:
|
||||||
choco upgrade managarr --version=0.5.0
|
choco upgrade managarr --version=0.7.0
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manual
|
### Manual
|
||||||
@@ -182,14 +181,30 @@ Key:
|
|||||||
| ✅ | ✅ | View and browse logs, tasks, events queues, and updates |
|
| ✅ | ✅ | View and browse logs, tasks, events queues, and updates |
|
||||||
| ✅ | ✅ | Manually trigger scheduled tasks |
|
| ✅ | ✅ | Manually trigger scheduled tasks |
|
||||||
|
|
||||||
|
### Lidarr
|
||||||
|
|
||||||
|
| TUI | CLI | Feature |
|
||||||
|
|-----|-----|----------------------------------------------------------------------------------------------------------------|
|
||||||
|
| ✅ | ✅ | View your library, downloads, blocklist, tracks |
|
||||||
|
| ✅ | ✅ | View details of a specific artists, albums, or tracks including description, history, downloaded file info |
|
||||||
|
| 🚫 | ✅ | View your host and security configs from the CLI to programmatically fetch the API token, among other settings |
|
||||||
|
| ✅ | ✅ | Search your library |
|
||||||
|
| ✅ | ✅ | Add artists to your library |
|
||||||
|
| ✅ | ✅ | Delete artists, downloads, indexers, root folders, and track files |
|
||||||
|
| ✅ | ✅ | Trigger automatic searches for artists or albums |
|
||||||
|
| ✅ | ✅ | Trigger refresh and disk scan for artists and downloads |
|
||||||
|
| ✅ | ✅ | Manually search for full artist discographies or albums |
|
||||||
|
| ✅ | ✅ | Edit your artists and indexers |
|
||||||
|
| ✅ | ✅ | Manage your tags |
|
||||||
|
| ✅ | ✅ | Manage your root folders |
|
||||||
|
| ✅ | ✅ | Manage your blocklist |
|
||||||
|
| ✅ | ✅ | View and browse logs, tasks, events queues, and updates |
|
||||||
|
| ✅ | ✅ | Manually trigger scheduled tasks |
|
||||||
|
|
||||||
### Readarr
|
### Readarr
|
||||||
|
|
||||||
- [ ] Support for Readarr
|
- [ ] Support for Readarr
|
||||||
|
|
||||||
### Lidarr
|
|
||||||
|
|
||||||
- [ ] Support for Lidarr
|
|
||||||
|
|
||||||
### Whisparr
|
### Whisparr
|
||||||
|
|
||||||
- [ ] Support for Whisparr
|
- [ ] Support for Whisparr
|
||||||
@@ -231,7 +246,7 @@ To see all available commands, simply run `managarr --help`:
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ managarr --help
|
$ managarr --help
|
||||||
managarr 0.5.1
|
managarr 0.7.0
|
||||||
Alex Clarke <alex.j.tusa@gmail.com>
|
Alex Clarke <alex.j.tusa@gmail.com>
|
||||||
|
|
||||||
A TUI and CLI to manage your Servarrs
|
A TUI and CLI to manage your Servarrs
|
||||||
@@ -241,20 +256,24 @@ Usage: managarr [OPTIONS] [COMMAND]
|
|||||||
Commands:
|
Commands:
|
||||||
radarr Commands for manging your Radarr instance
|
radarr Commands for manging your Radarr instance
|
||||||
sonarr Commands for manging your Sonarr instance
|
sonarr Commands for manging your Sonarr instance
|
||||||
|
lidarr Commands for manging your Lidarr instance
|
||||||
completions Generate shell completions for the Managarr CLI
|
completions Generate shell completions for the Managarr CLI
|
||||||
tail-logs Tail Managarr logs
|
tail-logs Tail Managarr logs
|
||||||
help Print this message or the help of the given subcommand(s)
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
-h, --help Print help
|
||||||
|
-V, --version Print version
|
||||||
|
|
||||||
|
Global Options:
|
||||||
--disable-spinner Disable the spinner (can sometimes make parsing output challenging) [env: MANAGARR_DISABLE_SPINNER=]
|
--disable-spinner Disable the spinner (can sometimes make parsing output challenging) [env: MANAGARR_DISABLE_SPINNER=]
|
||||||
--config-file <CONFIG_FILE> The Managarr configuration file to use [env: MANAGARR_CONFIG_FILE=]
|
--config-file <CONFIG_FILE> The Managarr configuration file to use [env: MANAGARR_CONFIG_FILE=]
|
||||||
--themes-file <THEMES_FILE> The Managarr themes file to use [env: MANAGARR_THEMES_FILE=]
|
--themes-file <THEMES_FILE> The Managarr themes file to use [env: MANAGARR_THEMES_FILE=]
|
||||||
--theme <THEME> The name of the Managarr theme to use [env: MANAGARR_THEME=]
|
--theme <THEME> The name of the Managarr theme to use [env: MANAGARR_THEME=]
|
||||||
--servarr-name <SERVARR_NAME> For multi-instance configurations, you need to specify the name of the instance configuration that you want to use.
|
--servarr-name <SERVARR_NAME> For multi-instance configurations, you need to specify the name of the instance configuration that you want to use.
|
||||||
This is useful when you have multiple instances of the same Servarr defined in your config file.
|
|
||||||
By default, if left empty, the first configured Servarr instance listed in the config file will be used.
|
This is useful when you have multiple instances of the same Servarr defined in your config file.
|
||||||
-h, --help Print help
|
By default, if left empty, the first configured Servarr instance listed in the config file will be used.
|
||||||
-V, --version Print version
|
|
||||||
```
|
```
|
||||||
|
|
||||||
All subcommands also have detailed help menus to show you how to use them. For example, to see all available commands for Sonarr, you would run:
|
All subcommands also have detailed help menus to show you how to use them. For example, to see all available commands for Sonarr, you would run:
|
||||||
@@ -283,12 +302,21 @@ Commands:
|
|||||||
test-all-indexers Test all Sonarr indexers
|
test-all-indexers Test all Sonarr indexers
|
||||||
toggle-episode-monitoring Toggle monitoring for the specified episode
|
toggle-episode-monitoring Toggle monitoring for the specified episode
|
||||||
toggle-season-monitoring Toggle monitoring for the specified season that corresponds to the specified series ID
|
toggle-season-monitoring Toggle monitoring for the specified season that corresponds to the specified series ID
|
||||||
|
toggle-series-monitoring Toggle monitoring for the specified series corresponding to the given series ID
|
||||||
help Print this message or the help of the given subcommand(s)
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--disable-spinner Disable the spinner (can sometimes make parsing output challenging) [env: MANAGARR_DISABLE_SPINNER=]
|
-h, --help Print help
|
||||||
--config <CONFIG> The Managarr configuration file to use [env: MANAGARR_CONFIG_FILE=]
|
|
||||||
-h, --help Print help
|
Global Options:
|
||||||
|
--disable-spinner Disable the spinner (can sometimes make parsing output challenging) [env: MANAGARR_DISABLE_SPINNER=]
|
||||||
|
--config-file <CONFIG_FILE> The Managarr configuration file to use [env: MANAGARR_CONFIG_FILE=]
|
||||||
|
--themes-file <THEMES_FILE> The Managarr themes file to use [env: MANAGARR_THEMES_FILE=]
|
||||||
|
--theme <THEME> The name of the Managarr theme to use [env: MANAGARR_THEME=]
|
||||||
|
--servarr-name <SERVARR_NAME> For multi-instance configurations, you need to specify the name of the instance configuration that you want to use.
|
||||||
|
|
||||||
|
This is useful when you have multiple instances of the same Servarr defined in your config file.
|
||||||
|
By default, if left empty, the first configured Servarr instance listed in the config file will be used.
|
||||||
```
|
```
|
||||||
|
|
||||||
**Pro Tip:** The CLI is even more powerful and useful when used in conjunction with the `jq` CLI tool. This allows you to parse the JSON response from the Managarr CLI and use it in your scripts; For example, to extract the `movieId` of the movie "Ad Astra", you would run:
|
**Pro Tip:** The CLI is even more powerful and useful when used in conjunction with the `jq` CLI tool. This allows you to parse the JSON response from the Managarr CLI and use it in your scripts; For example, to extract the `movieId` of the movie "Ad Astra", you would run:
|
||||||
@@ -428,9 +456,6 @@ Managarr supports using environment variables on startup so you don't have to al
|
|||||||
| `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` |
|
| `MANAGARR_DISABLE_SPINNER` | Disable the CLI spinner (this can be useful when scripting and parsing output) | `--disable-spinner` |
|
||||||
|
|
||||||
## Track What I'm Currently Working On
|
|
||||||
To see what feature(s) I'm currently working on, check out my [Wekan Board](https://wekan.alexjclarke.com/b/dHoGjBb44MHM9HSv4/managarr).
|
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
### Radarr
|
### Radarr
|
||||||
@@ -446,6 +471,13 @@ To see what feature(s) I'm currently working on, check out my [Wekan Board](http
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
### Lidarr
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
### General
|
### General
|
||||||

|

|
||||||

|

|
||||||
@@ -461,8 +493,8 @@ To see what feature(s) I'm currently working on, check out my [Wekan Board](http
|
|||||||
## Servarr Requirements
|
## Servarr Requirements
|
||||||
* [Radarr >= 5.3.6.8612](https://radarr.video/docs/api/)
|
* [Radarr >= 5.3.6.8612](https://radarr.video/docs/api/)
|
||||||
* [Sonarr >= v4](https://sonarr.tv/docs/api/)
|
* [Sonarr >= v4](https://sonarr.tv/docs/api/)
|
||||||
* [Readarr v1](https://readarr.com/docs/api/)
|
|
||||||
* [Lidarr v1](https://lidarr.audio/docs/api/)
|
* [Lidarr v1](https://lidarr.audio/docs/api/)
|
||||||
|
* [Readarr v1](https://readarr.com/docs/api/)
|
||||||
* [Whisparr >= v3](https://whisparr.com/docs/api/)
|
* [Whisparr >= v3](https://whisparr.com/docs/api/)
|
||||||
* [Prowlarr v1](https://prowlarr.com/docs/api/)
|
* [Prowlarr v1](https://prowlarr.com/docs/api/)
|
||||||
* [Bazarr v1.1.4](http://localhost:6767/api)
|
* [Bazarr v1.1.4](http://localhost:6767/api)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
VERSION := "latest"
|
VERSION := "latest"
|
||||||
IMG_NAME := "darkalex17/managarr"
|
IMG_NAME := "darkalex17/managarr"
|
||||||
IMAGE := "{{IMG_NAME}}:{{VERSION}}"
|
|
||||||
|
|
||||||
|
|
||||||
# List all recipes
|
# List all recipes
|
||||||
default:
|
default:
|
||||||
@@ -88,4 +86,4 @@ build build_type='debug':
|
|||||||
# Build the docker image
|
# Build the docker image
|
||||||
[group: 'build']
|
[group: 'build']
|
||||||
build-docker:
|
build-docker:
|
||||||
@DOCKER_BUILDKIT=1 docker build --rm -t {{IMAGE}}
|
@DOCKER_BUILDKIT=1 docker build --rm -t {{IMG_NAME}}:{{VERSION}} .
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 194 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 204 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 252 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 187 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 319 KiB |
@@ -57,6 +57,24 @@ mod tests {
|
|||||||
assert_eq!(app.tick_count, 0);
|
assert_eq!(app.tick_count, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_blocklist_block() {
|
||||||
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.data.lidarr_data.artists.set_items(vec![artist()]);
|
||||||
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::Blocklist)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetBlocklist.into());
|
||||||
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_artist_history_block() {
|
async fn test_dispatch_by_artist_history_block() {
|
||||||
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ impl App<'_> {
|
|||||||
.dispatch_network_event(LidarrEvent::ListArtists.into())
|
.dispatch_network_event(LidarrEvent::ListArtists.into())
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
ActiveLidarrBlock::Blocklist => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetBlocklist.into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
ActiveLidarrBlock::Downloads => {
|
ActiveLidarrBlock::Downloads => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(LidarrEvent::GetDownloads(500).into())
|
.dispatch_network_event(LidarrEvent::GetDownloads(500).into())
|
||||||
|
|||||||
@@ -58,21 +58,19 @@ impl App<'_> {
|
|||||||
}
|
}
|
||||||
ActiveSonarrBlock::SeasonHistory => {
|
ActiveSonarrBlock::SeasonHistory => {
|
||||||
if !self.data.sonarr_data.seasons.is_empty() {
|
if !self.data.sonarr_data.seasons.is_empty() {
|
||||||
|
let (series_id, season_number) = self.extract_series_id_season_number_tuple().await;
|
||||||
self
|
self
|
||||||
.dispatch_network_event(
|
.dispatch_network_event(SonarrEvent::GetSeasonHistory(series_id, season_number).into())
|
||||||
SonarrEvent::GetSeasonHistory(self.extract_series_id_season_number_tuple().await)
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::ManualSeasonSearch => {
|
ActiveSonarrBlock::ManualSeasonSearch => {
|
||||||
match self.data.sonarr_data.season_details_modal.as_ref() {
|
match self.data.sonarr_data.season_details_modal.as_ref() {
|
||||||
Some(season_details_modal) if season_details_modal.season_releases.is_empty() => {
|
Some(season_details_modal) if season_details_modal.season_releases.is_empty() => {
|
||||||
|
let (series_id, season_number) = self.extract_series_id_season_number_tuple().await;
|
||||||
self
|
self
|
||||||
.dispatch_network_event(
|
.dispatch_network_event(
|
||||||
SonarrEvent::GetSeasonReleases(self.extract_series_id_season_number_tuple().await)
|
SonarrEvent::GetSeasonReleases(series_id, season_number).into(),
|
||||||
.into(),
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ mod tests {
|
|||||||
assert!(app.is_loading);
|
assert!(app.is_loading);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_network_rx.recv().await.unwrap(),
|
sync_network_rx.recv().await.unwrap(),
|
||||||
SonarrEvent::GetSeasonHistory((1, 1)).into()
|
SonarrEvent::GetSeasonHistory(1, 1).into()
|
||||||
);
|
);
|
||||||
assert!(!app.data.sonarr_data.prompt_confirm);
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
assert_eq!(app.tick_count, 0);
|
assert_eq!(app.tick_count, 0);
|
||||||
@@ -175,7 +175,7 @@ mod tests {
|
|||||||
assert!(app.is_loading);
|
assert!(app.is_loading);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_network_rx.recv().await.unwrap(),
|
sync_network_rx.recv().await.unwrap(),
|
||||||
SonarrEvent::GetSeasonReleases((1, 1)).into()
|
SonarrEvent::GetSeasonReleases(1, 1).into()
|
||||||
);
|
);
|
||||||
assert!(!app.data.sonarr_data.prompt_confirm);
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
assert_eq!(app.tick_count, 0);
|
assert_eq!(app.tick_count, 0);
|
||||||
|
|||||||
+36
-1
@@ -8,12 +8,18 @@ mod tests {
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::cli::lidarr::LidarrCommand;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
use crate::{
|
use crate::{
|
||||||
Cli,
|
Cli,
|
||||||
app::App,
|
app::App,
|
||||||
cli::{handle_command, mutex_flags_or_option, radarr::RadarrCommand, sonarr::SonarrCommand},
|
cli::{handle_command, mutex_flags_or_option, radarr::RadarrCommand, sonarr::SonarrCommand},
|
||||||
models::{
|
models::{
|
||||||
Serdeable,
|
Serdeable,
|
||||||
|
lidarr_models::{
|
||||||
|
BlocklistItem as LidarrBlocklistItem, BlocklistResponse as LidarrBlocklistResponse,
|
||||||
|
LidarrSerdeable,
|
||||||
|
},
|
||||||
radarr_models::{
|
radarr_models::{
|
||||||
BlocklistItem as RadarrBlocklistItem, BlocklistResponse as RadarrBlocklistResponse,
|
BlocklistItem as RadarrBlocklistItem, BlocklistResponse as RadarrBlocklistResponse,
|
||||||
RadarrSerdeable,
|
RadarrSerdeable,
|
||||||
@@ -182,5 +188,34 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement test_cli_handler_delegates_lidarr_commands_to_the_lidarr_cli_handler
|
#[tokio::test]
|
||||||
|
async fn test_cli_handler_delegates_lidarr_commands_to_the_lidarr_cli_handler() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(LidarrEvent::GetBlocklist.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::BlocklistResponse(
|
||||||
|
LidarrBlocklistResponse {
|
||||||
|
records: vec![LidarrBlocklistItem::default()],
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(LidarrEvent::ClearBlocklist.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let clear_blocklist_command = LidarrCommand::ClearBlocklist.into();
|
||||||
|
|
||||||
|
let result = handle_command(&app_arc, clear_blocklist_command, &mut mock_network).await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,15 @@ pub enum LidarrDeleteCommand {
|
|||||||
#[arg(long, help = "Add a list exclusion for this album")]
|
#[arg(long, help = "Add a list exclusion for this album")]
|
||||||
add_list_exclusion: bool,
|
add_list_exclusion: bool,
|
||||||
},
|
},
|
||||||
|
#[command(about = "Delete the specified item from the Lidarr blocklist")]
|
||||||
|
BlocklistItem {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The ID of the blocklist item to remove from the blocklist",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
blocklist_item_id: i64,
|
||||||
|
},
|
||||||
#[command(about = "Delete the specified track file from disk")]
|
#[command(about = "Delete the specified track file from disk")]
|
||||||
TrackFile {
|
TrackFile {
|
||||||
#[arg(long, help = "The ID of the track file to delete", required = true)]
|
#[arg(long, help = "The ID of the track file to delete", required = true)]
|
||||||
@@ -107,6 +116,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrDeleteCommand> for LidarrDeleteComm
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
LidarrDeleteCommand::BlocklistItem { blocklist_item_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::DeleteBlocklistItem(blocklist_item_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
LidarrDeleteCommand::TrackFile { track_file_id } => {
|
LidarrDeleteCommand::TrackFile { track_file_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
|
|||||||
@@ -86,6 +86,42 @@ mod tests {
|
|||||||
assert_eq!(delete_command, expected_args);
|
assert_eq!(delete_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_blocklist_item_requires_arguments() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "blocklist-item"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_blocklist_item_success() {
|
||||||
|
let expected_args = LidarrDeleteCommand::BlocklistItem {
|
||||||
|
blocklist_item_id: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"delete",
|
||||||
|
"blocklist-item",
|
||||||
|
"--blocklist-item-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_track_file_requires_arguments() {
|
fn test_delete_track_file_requires_arguments() {
|
||||||
let result =
|
let result =
|
||||||
@@ -361,6 +397,37 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_delete_blocklist_item_command() {
|
||||||
|
let expected_blocklist_item_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::DeleteBlocklistItem(expected_blocklist_item_id).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let delete_blocklist_item_command = LidarrDeleteCommand::BlocklistItem {
|
||||||
|
blocklist_item_id: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LidarrDeleteCommandHandler::with(
|
||||||
|
&app_arc,
|
||||||
|
delete_blocklist_item_command,
|
||||||
|
&mut mock_network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_delete_track_file_command() {
|
async fn test_handle_delete_track_file_command() {
|
||||||
let expected_track_file_id = 1;
|
let expected_track_file_id = 1;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ mod tests {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_commands_that_have_no_arg_requirements(
|
fn test_commands_that_have_no_arg_requirements(
|
||||||
#[values("test-all-indexers")] subcommand: &str,
|
#[values("clear-blocklist", "test-all-indexers")] subcommand: &str,
|
||||||
) {
|
) {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", subcommand]);
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", subcommand]);
|
||||||
|
|
||||||
@@ -284,7 +284,9 @@ 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::{LidarrReleaseDownloadBody, LidarrTaskName};
|
use crate::models::lidarr_models::{
|
||||||
|
BlocklistItem, BlocklistResponse, LidarrReleaseDownloadBody, LidarrTaskName,
|
||||||
|
};
|
||||||
use crate::models::servarr_models::IndexerSettings;
|
use crate::models::servarr_models::IndexerSettings;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
@@ -546,6 +548,39 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_clear_blocklist_command() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(LidarrEvent::GetBlocklist.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::BlocklistResponse(
|
||||||
|
BlocklistResponse {
|
||||||
|
records: vec![BlocklistItem::default()],
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(LidarrEvent::ClearBlocklist.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let claer_blocklist_command = LidarrCommand::ClearBlocklist;
|
||||||
|
|
||||||
|
let result = LidarrCliHandler::with(&app_arc, claer_blocklist_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_download_release_command() {
|
async fn test_download_release_command() {
|
||||||
let expected_release_download_body = LidarrReleaseDownloadBody {
|
let expected_release_download_body = LidarrReleaseDownloadBody {
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ pub enum LidarrListCommand {
|
|||||||
},
|
},
|
||||||
#[command(about = "List all artists in your Lidarr library")]
|
#[command(about = "List all artists in your Lidarr library")]
|
||||||
Artists,
|
Artists,
|
||||||
|
#[command(about = "List all items in the Lidarr blocklist")]
|
||||||
|
Blocklist,
|
||||||
#[command(about = "List all active downloads in Lidarr")]
|
#[command(about = "List all active downloads in Lidarr")]
|
||||||
Downloads {
|
Downloads {
|
||||||
#[arg(long, help = "How many downloads to fetch", default_value_t = 500)]
|
#[arg(long, help = "How many downloads to fetch", default_value_t = 500)]
|
||||||
@@ -200,6 +202,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrListCommand> for LidarrListCommandH
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
LidarrListCommand::Blocklist => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetBlocklist.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
LidarrListCommand::Downloads { count } => {
|
LidarrListCommand::Downloads { count } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ mod tests {
|
|||||||
fn test_list_commands_have_no_arg_requirements(
|
fn test_list_commands_have_no_arg_requirements(
|
||||||
#[values(
|
#[values(
|
||||||
"artists",
|
"artists",
|
||||||
|
"blocklist",
|
||||||
"indexers",
|
"indexers",
|
||||||
"metadata-profiles",
|
"metadata-profiles",
|
||||||
"quality-profiles",
|
"quality-profiles",
|
||||||
@@ -433,6 +434,7 @@ mod tests {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(LidarrListCommand::Artists, LidarrEvent::ListArtists)]
|
#[case(LidarrListCommand::Artists, LidarrEvent::ListArtists)]
|
||||||
|
#[case(LidarrListCommand::Blocklist, LidarrEvent::GetBlocklist)]
|
||||||
#[case(LidarrListCommand::Indexers, LidarrEvent::GetIndexers)]
|
#[case(LidarrListCommand::Indexers, LidarrEvent::GetIndexers)]
|
||||||
#[case(LidarrListCommand::MetadataProfiles, LidarrEvent::GetMetadataProfiles)]
|
#[case(LidarrListCommand::MetadataProfiles, LidarrEvent::GetMetadataProfiles)]
|
||||||
#[case(LidarrListCommand::QualityProfiles, LidarrEvent::GetQualityProfiles)]
|
#[case(LidarrListCommand::QualityProfiles, LidarrEvent::GetQualityProfiles)]
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ 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 = "Clear the Lidarr blocklist")]
|
||||||
|
ClearBlocklist,
|
||||||
#[command(about = "Manually download the given release")]
|
#[command(about = "Manually download the given release")]
|
||||||
DownloadRelease {
|
DownloadRelease {
|
||||||
#[arg(long, help = "The GUID of the release to download", required = true)]
|
#[arg(long, help = "The GUID of the release to download", required = true)]
|
||||||
@@ -217,6 +219,17 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, '
|
|||||||
.handle()
|
.handle()
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
LidarrCommand::ClearBlocklist => {
|
||||||
|
self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetBlocklist.into())
|
||||||
|
.await?;
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::ClearBlocklist.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
LidarrCommand::DownloadRelease { guid, indexer_id } => {
|
LidarrCommand::DownloadRelease { guid, indexer_id } => {
|
||||||
let params = LidarrReleaseDownloadBody { guid, indexer_id };
|
let params = LidarrReleaseDownloadBody { guid, indexer_id };
|
||||||
let resp = self
|
let resp = self
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrListCommand> for SonarrListCommandH
|
|||||||
} => {
|
} => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(SonarrEvent::GetSeasonHistory((series_id, season_number)).into())
|
.handle_network_event(SonarrEvent::GetSeasonHistory(series_id, season_number).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -543,7 +543,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
SonarrEvent::GetSeasonHistory((expected_series_id, expected_season_number)).into(),
|
SonarrEvent::GetSeasonHistory(expected_series_id, expected_season_number).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ pub enum SonarrManualSearchCommand {
|
|||||||
episode_id: i64,
|
episode_id: i64,
|
||||||
},
|
},
|
||||||
#[command(
|
#[command(
|
||||||
about = "Trigger a manual search of releases for the given season corresponding to the series with the given ID"
|
about = "Trigger a manual search of full-season releases (full_season: true) for the given season corresponding to the series with the given ID"
|
||||||
)]
|
)]
|
||||||
Season {
|
Season {
|
||||||
#[arg(
|
#[arg(
|
||||||
@@ -88,7 +88,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrManualSearchCommand>
|
|||||||
serde_json::to_string_pretty(&seasons_vec)?
|
serde_json::to_string_pretty(&seasons_vec)?
|
||||||
}
|
}
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
_ => serde_json::to_string_pretty(&json!({"message": "Failed to parse response"}))?,
|
_ => serde_json::to_string_pretty(&json!({"message": "Unexpected response format"}))?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SonarrManualSearchCommand::Season {
|
SonarrManualSearchCommand::Season {
|
||||||
@@ -98,7 +98,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrManualSearchCommand>
|
|||||||
println!("Searching for season releases. This may take a minute...");
|
println!("Searching for season releases. This may take a minute...");
|
||||||
match self
|
match self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(SonarrEvent::GetSeasonReleases((series_id, season_number)).into())
|
.handle_network_event(SonarrEvent::GetSeasonReleases(series_id, season_number).into())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(Serdeable::Sonarr(SonarrSerdeable::Releases(releases_vec))) => {
|
Ok(Serdeable::Sonarr(SonarrSerdeable::Releases(releases_vec))) => {
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
SonarrEvent::GetSeasonReleases((expected_series_id, expected_season_number)).into(),
|
SonarrEvent::GetSeasonReleases(expected_series_id, expected_season_number).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, '
|
|||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(
|
.handle_network_event(
|
||||||
SonarrEvent::ToggleSeasonMonitoring((series_id, season_number)).into(),
|
SonarrEvent::ToggleSeasonMonitoring(series_id, season_number).into(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
|||||||
@@ -755,7 +755,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
SonarrEvent::ToggleSeasonMonitoring((expected_series_id, expected_season_number)).into(),
|
SonarrEvent::ToggleSeasonMonitoring(expected_series_id, expected_season_number).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrTriggerAutomaticSearchCommand>
|
|||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(
|
.handle_network_event(
|
||||||
SonarrEvent::TriggerAutomaticSeasonSearch((series_id, season_number)).into(),
|
SonarrEvent::TriggerAutomaticSeasonSearch(series_id, season_number).into(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
SonarrEvent::TriggerAutomaticSeasonSearch((expected_series_id, expected_season_number))
|
SonarrEvent::TriggerAutomaticSeasonSearch(expected_series_id, expected_season_number)
|
||||||
.into(),
|
.into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
|
|||||||
@@ -0,0 +1,615 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use chrono::DateTime;
|
||||||
|
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||||
|
use rstest::rstest;
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::assert_navigation_pushed;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::KeyEventHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::blocklist::{BlocklistHandler, blocklist_sorting_options};
|
||||||
|
use crate::models::lidarr_models::{Artist, BlocklistItem};
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, BLOCKLIST_BLOCKS};
|
||||||
|
use crate::models::servarr_models::{Quality, QualityWrapper};
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::artist;
|
||||||
|
|
||||||
|
mod test_handle_delete {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_blocklist_item_prompt() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
|
||||||
|
BlocklistHandler::new(DELETE_KEY, &mut app, ActiveLidarrBlock::Blocklist, None).handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::DeleteBlocklistItemPrompt.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_blocklist_item_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
|
||||||
|
BlocklistHandler::new(DELETE_KEY, &mut app, ActiveLidarrBlock::Blocklist, None).handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Blocklist.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_left_right_action {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_navigation_pushed;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_blocklist_tab_left(#[values(true, false)] is_ready: bool) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(2);
|
||||||
|
|
||||||
|
BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.left.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
|
ActiveLidarrBlock::Downloads.into()
|
||||||
|
);
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::Downloads.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_blocklist_tab_right(#[values(true, false)] is_ready: bool) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(2);
|
||||||
|
|
||||||
|
BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.right.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
|
ActiveLidarrBlock::History.into()
|
||||||
|
);
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::History.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_blocklist_left_right_prompt_toggle(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt,
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
#[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
|
||||||
|
BlocklistHandler::new(key, &mut app, active_lidarr_block, None).handle();
|
||||||
|
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
|
||||||
|
BlocklistHandler::new(key, &mut app, active_lidarr_block, None).handle();
|
||||||
|
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_submit {
|
||||||
|
use crate::assert_navigation_popped;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_submit() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
|
||||||
|
BlocklistHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::Blocklist, None).handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::BlocklistItemDetails.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_submit_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
|
||||||
|
BlocklistHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::Blocklist, None).handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Blocklist.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt,
|
||||||
|
LidarrEvent::DeleteBlocklistItem(3)
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt,
|
||||||
|
LidarrEvent::ClearBlocklist
|
||||||
|
)]
|
||||||
|
fn test_blocklist_prompt_confirm_submit(
|
||||||
|
#[case] base_route: ActiveLidarrBlock,
|
||||||
|
#[case] prompt_block: ActiveLidarrBlock,
|
||||||
|
#[case] expected_action: LidarrEvent,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.push_navigation_stack(base_route.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
BlocklistHandler::new(SUBMIT_KEY, &mut app, prompt_block, None).handle();
|
||||||
|
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
&app.data.lidarr_data.prompt_confirm_action,
|
||||||
|
&expected_action
|
||||||
|
);
|
||||||
|
assert_navigation_popped!(app, base_route.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_blocklist_prompt_decline_submit(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt,
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt
|
||||||
|
)]
|
||||||
|
prompt_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
BlocklistHandler::new(SUBMIT_KEY, &mut app, prompt_block, None).handle();
|
||||||
|
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_none!(app.data.lidarr_data.prompt_confirm_action);
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::Blocklist.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_esc {
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_navigation_popped;
|
||||||
|
|
||||||
|
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt
|
||||||
|
)]
|
||||||
|
fn test_blocklist_prompt_blocks_esc(
|
||||||
|
#[case] base_block: ActiveLidarrBlock,
|
||||||
|
#[case] prompt_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(base_block.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
|
||||||
|
BlocklistHandler::new(ESC_KEY, &mut app, prompt_block, None).handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, base_block.into());
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_esc_blocklist_item_details() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::BlocklistItemDetails.into());
|
||||||
|
|
||||||
|
BlocklistHandler::new(
|
||||||
|
ESC_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::BlocklistItemDetails,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::Blocklist.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_default_esc(#[values(true, false)] is_ready: bool) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.error = "test error".to_owned().into();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
|
||||||
|
BlocklistHandler::new(ESC_KEY, &mut app, ActiveLidarrBlock::Blocklist, None).handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::Blocklist.into());
|
||||||
|
assert_is_empty!(app.error.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_key_char {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{assert_navigation_popped, assert_navigation_pushed};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refresh_blocklist_key() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
|
||||||
|
BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::Blocklist.into());
|
||||||
|
assert!(app.should_refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refresh_blocklist_key_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
|
||||||
|
BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Blocklist.into());
|
||||||
|
assert!(!app.should_refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clear_blocklist_key() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
|
||||||
|
BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.clear.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::BlocklistClearAllItemsPrompt.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clear_blocklist_key_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
|
||||||
|
BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.clear.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Blocklist.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt,
|
||||||
|
LidarrEvent::DeleteBlocklistItem(3)
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt,
|
||||||
|
LidarrEvent::ClearBlocklist
|
||||||
|
)]
|
||||||
|
fn test_blocklist_prompt_confirm(
|
||||||
|
#[case] base_route: ActiveLidarrBlock,
|
||||||
|
#[case] prompt_block: ActiveLidarrBlock,
|
||||||
|
#[case] expected_action: LidarrEvent,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
app.push_navigation_stack(base_route.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.confirm.key,
|
||||||
|
&mut app,
|
||||||
|
prompt_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
&app.data.lidarr_data.prompt_confirm_action,
|
||||||
|
&expected_action
|
||||||
|
);
|
||||||
|
assert_navigation_popped!(app, base_route.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_sorting_options_artist_name() {
|
||||||
|
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering = |a, b| {
|
||||||
|
a.artist
|
||||||
|
.artist_name
|
||||||
|
.text
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.artist.artist_name.text.to_lowercase())
|
||||||
|
};
|
||||||
|
let mut expected_blocklist_vec = blocklist_vec();
|
||||||
|
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = blocklist_sorting_options()[0].clone();
|
||||||
|
let mut sorted_blocklist_vec = blocklist_vec();
|
||||||
|
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Artist Name");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_sorting_options_source_title() {
|
||||||
|
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering = |a, b| {
|
||||||
|
a.source_title
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.source_title.to_lowercase())
|
||||||
|
};
|
||||||
|
let mut expected_blocklist_vec = blocklist_vec();
|
||||||
|
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = blocklist_sorting_options()[1].clone();
|
||||||
|
let mut sorted_blocklist_vec = blocklist_vec();
|
||||||
|
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Source Title");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_sorting_options_quality() {
|
||||||
|
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering = |a, b| {
|
||||||
|
a.quality
|
||||||
|
.quality
|
||||||
|
.name
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.quality.quality.name.to_lowercase())
|
||||||
|
};
|
||||||
|
let mut expected_blocklist_vec = blocklist_vec();
|
||||||
|
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = blocklist_sorting_options()[2].clone();
|
||||||
|
let mut sorted_blocklist_vec = blocklist_vec();
|
||||||
|
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Quality");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_sorting_options_date() {
|
||||||
|
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering =
|
||||||
|
|a, b| a.date.cmp(&b.date);
|
||||||
|
let mut expected_blocklist_vec = blocklist_vec();
|
||||||
|
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = blocklist_sorting_options()[3].clone();
|
||||||
|
let mut sorted_blocklist_vec = blocklist_vec();
|
||||||
|
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Date");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_handler_accepts() {
|
||||||
|
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
||||||
|
if BLOCKLIST_BLOCKS.contains(&active_lidarr_block) {
|
||||||
|
assert!(BlocklistHandler::accepts(active_lidarr_block));
|
||||||
|
} else {
|
||||||
|
assert!(!BlocklistHandler::accepts(active_lidarr_block));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_blocklist_handler_ignore_special_keys(
|
||||||
|
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
|
||||||
|
let handler = BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::default(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
handler.ignore_special_keys(),
|
||||||
|
ignore_special_keys_for_textbox_input
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_blocklist_item_id() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
|
||||||
|
let blocklist_item_id = BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.extract_blocklist_item_id();
|
||||||
|
|
||||||
|
assert_eq!(blocklist_item_id, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_handler_not_ready_when_loading() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.is_loading = true;
|
||||||
|
|
||||||
|
let handler = BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_handler_not_ready_when_blocklist_is_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
|
||||||
|
let handler = BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_handler_ready_when_not_loading_and_blocklist_is_not_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.blocklist
|
||||||
|
.set_items(vec![BlocklistItem::default()]);
|
||||||
|
|
||||||
|
let handler = BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blocklist_vec() -> Vec<BlocklistItem> {
|
||||||
|
vec![
|
||||||
|
BlocklistItem {
|
||||||
|
id: 3,
|
||||||
|
source_title: "test 1".to_owned(),
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "Lossless".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
date: DateTime::from(DateTime::parse_from_rfc3339("2024-01-10T07:28:45Z").unwrap()),
|
||||||
|
artist: Artist {
|
||||||
|
artist_name: "test 3".into(),
|
||||||
|
..artist()
|
||||||
|
},
|
||||||
|
..BlocklistItem::default()
|
||||||
|
},
|
||||||
|
BlocklistItem {
|
||||||
|
id: 2,
|
||||||
|
source_title: "test 2".to_owned(),
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "Lossy".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
date: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()),
|
||||||
|
artist: Artist {
|
||||||
|
artist_name: "test 2".into(),
|
||||||
|
..artist()
|
||||||
|
},
|
||||||
|
..BlocklistItem::default()
|
||||||
|
},
|
||||||
|
BlocklistItem {
|
||||||
|
id: 1,
|
||||||
|
source_title: "test 3".to_owned(),
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "Lossless".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
date: DateTime::from(DateTime::parse_from_rfc3339("2024-03-10T07:28:45Z").unwrap()),
|
||||||
|
artist: Artist {
|
||||||
|
artist_name: "".into(),
|
||||||
|
..artist()
|
||||||
|
},
|
||||||
|
..BlocklistItem::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
use crate::app::App;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::lidarr_handlers::handle_change_tab_left_right_keys;
|
||||||
|
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||||
|
use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle};
|
||||||
|
use crate::matches_key;
|
||||||
|
use crate::models::Route;
|
||||||
|
use crate::models::lidarr_models::BlocklistItem;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, BLOCKLIST_BLOCKS};
|
||||||
|
use crate::models::stateful_table::SortOption;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "blocklist_handler_tests.rs"]
|
||||||
|
mod blocklist_handler_tests;
|
||||||
|
|
||||||
|
pub(super) struct BlocklistHandler<'a, 'b> {
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
_context: Option<ActiveLidarrBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlocklistHandler<'_, '_> {
|
||||||
|
fn extract_blocklist_item_id(&self) -> i64 {
|
||||||
|
self.app.data.lidarr_data.blocklist.current_selection().id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for BlocklistHandler<'a, 'b> {
|
||||||
|
fn handle(&mut self) {
|
||||||
|
let blocklist_table_handling_config =
|
||||||
|
TableHandlingConfig::new(ActiveLidarrBlock::Blocklist.into())
|
||||||
|
.sorting_block(ActiveLidarrBlock::BlocklistSortPrompt.into())
|
||||||
|
.sort_options(blocklist_sorting_options());
|
||||||
|
|
||||||
|
if !handle_table(
|
||||||
|
self,
|
||||||
|
|app| &mut app.data.lidarr_data.blocklist,
|
||||||
|
blocklist_table_handling_config,
|
||||||
|
) {
|
||||||
|
self.handle_key_event();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||||
|
BLOCKLIST_BLOCKS.contains(&active_block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_block: ActiveLidarrBlock,
|
||||||
|
context: Option<ActiveLidarrBlock>,
|
||||||
|
) -> Self {
|
||||||
|
BlocklistHandler {
|
||||||
|
key,
|
||||||
|
app,
|
||||||
|
active_lidarr_block: active_block,
|
||||||
|
_context: context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key(&self) -> Key {
|
||||||
|
self.key
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ignore_special_keys(&self) -> bool {
|
||||||
|
self.app.ignore_special_keys_for_textbox_input
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_ready(&self) -> bool {
|
||||||
|
!self.app.is_loading && !self.app.data.lidarr_data.blocklist.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_scroll_up(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_scroll_down(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_home(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_end(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_delete(&mut self) {
|
||||||
|
if self.active_lidarr_block == ActiveLidarrBlock::Blocklist {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::DeleteBlocklistItemPrompt.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_left_right_action(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::Blocklist => handle_change_tab_left_right_keys(self.app, self.key),
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt
|
||||||
|
| ActiveLidarrBlock::BlocklistClearAllItemsPrompt => handle_prompt_toggle(self.app, self.key),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_submit(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt => {
|
||||||
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::DeleteBlocklistItem(
|
||||||
|
self.extract_blocklist_item_id(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt => {
|
||||||
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::ClearBlocklist);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::Blocklist => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::BlocklistItemDetails.into());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_esc(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt
|
||||||
|
| ActiveLidarrBlock::BlocklistClearAllItemsPrompt => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = false;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::BlocklistItemDetails | ActiveLidarrBlock::BlocklistSortPrompt => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
_ => handle_clear_errors(self.app),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_char_key_event(&mut self) {
|
||||||
|
let key = self.key;
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::Blocklist => match self.key {
|
||||||
|
_ if matches_key!(refresh, key) => {
|
||||||
|
self.app.should_refresh = true;
|
||||||
|
}
|
||||||
|
_ if matches_key!(clear, key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::BlocklistClearAllItemsPrompt.into());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt => {
|
||||||
|
if matches_key!(confirm, key) {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::DeleteBlocklistItem(
|
||||||
|
self.extract_blocklist_item_id(),
|
||||||
|
));
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt => {
|
||||||
|
if matches_key!(confirm, key) {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::ClearBlocklist);
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app_mut(&mut self) -> &mut App<'b> {
|
||||||
|
self.app
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_route(&self) -> Route {
|
||||||
|
self.app.get_current_route()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blocklist_sorting_options() -> Vec<SortOption<BlocklistItem>> {
|
||||||
|
vec![
|
||||||
|
SortOption {
|
||||||
|
name: "Artist Name",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
a.artist
|
||||||
|
.artist_name
|
||||||
|
.text
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.artist.artist_name.text.to_lowercase())
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Source Title",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
a.source_title
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.source_title.to_lowercase())
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Quality",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
a.quality
|
||||||
|
.quality
|
||||||
|
.name
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.quality.quality.name.to_lowercase())
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Date",
|
||||||
|
cmp_fn: Some(|a, b| a.date.cmp(&b.date)),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -99,9 +99,9 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.data.lidarr_data.main_tabs.get_active_route(),
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
ActiveLidarrBlock::History.into()
|
ActiveLidarrBlock::Blocklist.into()
|
||||||
);
|
);
|
||||||
assert_navigation_pushed!(app, ActiveLidarrBlock::History.into());
|
assert_navigation_pushed!(app, ActiveLidarrBlock::Blocklist.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ mod tests {
|
|||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
app.is_loading = is_ready;
|
app.is_loading = is_ready;
|
||||||
app.data.lidarr_data.main_tabs.set_index(2);
|
app.data.lidarr_data.main_tabs.set_index(3);
|
||||||
|
|
||||||
HistoryHandler::new(
|
HistoryHandler::new(
|
||||||
DEFAULT_KEYBINDINGS.left.key,
|
DEFAULT_KEYBINDINGS.left.key,
|
||||||
@@ -41,9 +41,9 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.data.lidarr_data.main_tabs.get_active_route(),
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
ActiveLidarrBlock::Downloads.into()
|
ActiveLidarrBlock::Blocklist.into()
|
||||||
);
|
);
|
||||||
assert_navigation_pushed!(app, ActiveLidarrBlock::Downloads.into());
|
assert_navigation_pushed!(app, ActiveLidarrBlock::Blocklist.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
@@ -51,7 +51,7 @@ mod tests {
|
|||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
app.is_loading = is_ready;
|
app.is_loading = is_ready;
|
||||||
app.data.lidarr_data.main_tabs.set_index(2);
|
app.data.lidarr_data.main_tabs.set_index(3);
|
||||||
|
|
||||||
HistoryHandler::new(
|
HistoryHandler::new(
|
||||||
DEFAULT_KEYBINDINGS.right.key,
|
DEFAULT_KEYBINDINGS.right.key,
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ mod tests {
|
|||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
|
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
|
||||||
app.is_loading = is_ready;
|
app.is_loading = is_ready;
|
||||||
app.data.lidarr_data.main_tabs.set_index(4);
|
app.data.lidarr_data.main_tabs.set_index(5);
|
||||||
|
|
||||||
IndexersHandler::new(
|
IndexersHandler::new(
|
||||||
DEFAULT_KEYBINDINGS.left.key,
|
DEFAULT_KEYBINDINGS.left.key,
|
||||||
@@ -89,7 +89,7 @@ mod tests {
|
|||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
|
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
|
||||||
app.is_loading = is_ready;
|
app.is_loading = is_ready;
|
||||||
app.data.lidarr_data.main_tabs.set_index(4);
|
app.data.lidarr_data.main_tabs.set_index(5);
|
||||||
|
|
||||||
IndexersHandler::new(
|
IndexersHandler::new(
|
||||||
DEFAULT_KEYBINDINGS.right.key,
|
DEFAULT_KEYBINDINGS.right.key,
|
||||||
|
|||||||
@@ -53,11 +53,12 @@ mod tests {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(0, ActiveLidarrBlock::System, ActiveLidarrBlock::Downloads)]
|
#[case(0, ActiveLidarrBlock::System, ActiveLidarrBlock::Downloads)]
|
||||||
#[case(1, ActiveLidarrBlock::Artists, ActiveLidarrBlock::History)]
|
#[case(1, ActiveLidarrBlock::Artists, ActiveLidarrBlock::Blocklist)]
|
||||||
#[case(2, ActiveLidarrBlock::Downloads, ActiveLidarrBlock::RootFolders)]
|
#[case(2, ActiveLidarrBlock::Downloads, ActiveLidarrBlock::History)]
|
||||||
#[case(3, ActiveLidarrBlock::History, ActiveLidarrBlock::Indexers)]
|
#[case(3, ActiveLidarrBlock::Blocklist, ActiveLidarrBlock::RootFolders)]
|
||||||
#[case(4, ActiveLidarrBlock::RootFolders, ActiveLidarrBlock::System)]
|
#[case(4, ActiveLidarrBlock::History, ActiveLidarrBlock::Indexers)]
|
||||||
#[case(5, ActiveLidarrBlock::Indexers, ActiveLidarrBlock::Artists)]
|
#[case(5, ActiveLidarrBlock::RootFolders, ActiveLidarrBlock::System)]
|
||||||
|
#[case(6, ActiveLidarrBlock::Indexers, ActiveLidarrBlock::Artists)]
|
||||||
fn test_lidarr_handler_change_tab_left_right_keys(
|
fn test_lidarr_handler_change_tab_left_right_keys(
|
||||||
#[case] index: usize,
|
#[case] index: usize,
|
||||||
#[case] left_block: ActiveLidarrBlock,
|
#[case] left_block: ActiveLidarrBlock,
|
||||||
@@ -87,11 +88,12 @@ mod tests {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(0, ActiveLidarrBlock::System, ActiveLidarrBlock::Downloads)]
|
#[case(0, ActiveLidarrBlock::System, ActiveLidarrBlock::Downloads)]
|
||||||
#[case(1, ActiveLidarrBlock::Artists, ActiveLidarrBlock::History)]
|
#[case(1, ActiveLidarrBlock::Artists, ActiveLidarrBlock::Blocklist)]
|
||||||
#[case(2, ActiveLidarrBlock::Downloads, ActiveLidarrBlock::RootFolders)]
|
#[case(2, ActiveLidarrBlock::Downloads, ActiveLidarrBlock::History)]
|
||||||
#[case(3, ActiveLidarrBlock::History, ActiveLidarrBlock::Indexers)]
|
#[case(3, ActiveLidarrBlock::Blocklist, ActiveLidarrBlock::RootFolders)]
|
||||||
#[case(4, ActiveLidarrBlock::RootFolders, ActiveLidarrBlock::System)]
|
#[case(4, ActiveLidarrBlock::History, ActiveLidarrBlock::Indexers)]
|
||||||
#[case(5, ActiveLidarrBlock::Indexers, ActiveLidarrBlock::Artists)]
|
#[case(5, ActiveLidarrBlock::RootFolders, ActiveLidarrBlock::System)]
|
||||||
|
#[case(6, ActiveLidarrBlock::Indexers, ActiveLidarrBlock::Artists)]
|
||||||
fn test_lidarr_handler_change_tab_left_right_keys_alt_navigation(
|
fn test_lidarr_handler_change_tab_left_right_keys_alt_navigation(
|
||||||
#[case] index: usize,
|
#[case] index: usize,
|
||||||
#[case] left_block: ActiveLidarrBlock,
|
#[case] left_block: ActiveLidarrBlock,
|
||||||
@@ -122,10 +124,11 @@ mod tests {
|
|||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(0, ActiveLidarrBlock::Artists)]
|
#[case(0, ActiveLidarrBlock::Artists)]
|
||||||
#[case(1, ActiveLidarrBlock::Downloads)]
|
#[case(1, ActiveLidarrBlock::Downloads)]
|
||||||
#[case(2, ActiveLidarrBlock::History)]
|
#[case(2, ActiveLidarrBlock::Blocklist)]
|
||||||
#[case(3, ActiveLidarrBlock::RootFolders)]
|
#[case(3, ActiveLidarrBlock::History)]
|
||||||
#[case(4, ActiveLidarrBlock::Indexers)]
|
#[case(4, ActiveLidarrBlock::RootFolders)]
|
||||||
#[case(5, ActiveLidarrBlock::System)]
|
#[case(5, ActiveLidarrBlock::Indexers)]
|
||||||
|
#[case(6, ActiveLidarrBlock::System)]
|
||||||
fn test_lidarr_handler_change_tab_left_right_keys_alt_navigation_no_op_when_ignoring_quit_key(
|
fn test_lidarr_handler_change_tab_left_right_keys_alt_navigation_no_op_when_ignoring_quit_key(
|
||||||
#[case] index: usize,
|
#[case] index: usize,
|
||||||
#[case] block: ActiveLidarrBlock,
|
#[case] block: ActiveLidarrBlock,
|
||||||
@@ -197,6 +200,24 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_delegates_blocklist_blocks_to_blocklist_handler(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
ActiveLidarrBlock::BlocklistItemDetails,
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt,
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt,
|
||||||
|
ActiveLidarrBlock::BlocklistSortPrompt
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
test_handler_delegation!(
|
||||||
|
LidarrHandler,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
active_lidarr_block
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_delegates_history_blocks_to_history_handler(
|
fn test_delegates_history_blocks_to_history_handler(
|
||||||
#[values(
|
#[values(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use indexers::IndexersHandler;
|
|||||||
use library::LibraryHandler;
|
use library::LibraryHandler;
|
||||||
|
|
||||||
use super::KeyEventHandler;
|
use super::KeyEventHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::blocklist::BlocklistHandler;
|
||||||
use crate::handlers::lidarr_handlers::downloads::DownloadsHandler;
|
use crate::handlers::lidarr_handlers::downloads::DownloadsHandler;
|
||||||
use crate::handlers::lidarr_handlers::root_folders::RootFoldersHandler;
|
use crate::handlers::lidarr_handlers::root_folders::RootFoldersHandler;
|
||||||
use crate::handlers::lidarr_handlers::system::SystemHandler;
|
use crate::handlers::lidarr_handlers::system::SystemHandler;
|
||||||
@@ -11,6 +12,7 @@ use crate::{
|
|||||||
app::App, event::Key, matches_key, models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock,
|
app::App, event::Key, matches_key, models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod blocklist;
|
||||||
mod downloads;
|
mod downloads;
|
||||||
mod history;
|
mod history;
|
||||||
mod indexers;
|
mod indexers;
|
||||||
@@ -38,6 +40,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LidarrHandler<'a, 'b
|
|||||||
_ if DownloadsHandler::accepts(self.active_lidarr_block) => {
|
_ if DownloadsHandler::accepts(self.active_lidarr_block) => {
|
||||||
DownloadsHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
DownloadsHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
||||||
}
|
}
|
||||||
|
_ if BlocklistHandler::accepts(self.active_lidarr_block) => {
|
||||||
|
BlocklistHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
||||||
|
}
|
||||||
_ if HistoryHandler::accepts(self.active_lidarr_block) => {
|
_ if HistoryHandler::accepts(self.active_lidarr_block) => {
|
||||||
HistoryHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
HistoryHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ mod tests {
|
|||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
app.is_loading = is_ready;
|
app.is_loading = is_ready;
|
||||||
app.data.lidarr_data.main_tabs.set_index(3);
|
app.data.lidarr_data.main_tabs.set_index(4);
|
||||||
|
|
||||||
RootFoldersHandler::new(
|
RootFoldersHandler::new(
|
||||||
DEFAULT_KEYBINDINGS.left.key,
|
DEFAULT_KEYBINDINGS.left.key,
|
||||||
@@ -93,7 +93,7 @@ mod tests {
|
|||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
app.is_loading = is_ready;
|
app.is_loading = is_ready;
|
||||||
app.data.lidarr_data.main_tabs.set_index(3);
|
app.data.lidarr_data.main_tabs.set_index(4);
|
||||||
|
|
||||||
RootFoldersHandler::new(
|
RootFoldersHandler::new(
|
||||||
DEFAULT_KEYBINDINGS.right.key,
|
DEFAULT_KEYBINDINGS.right.key,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ mod tests {
|
|||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
app.is_loading = is_ready;
|
app.is_loading = is_ready;
|
||||||
app.data.lidarr_data.main_tabs.set_index(5);
|
app.data.lidarr_data.main_tabs.set_index(6);
|
||||||
|
|
||||||
SystemHandler::new(
|
SystemHandler::new(
|
||||||
DEFAULT_KEYBINDINGS.left.key,
|
DEFAULT_KEYBINDINGS.left.key,
|
||||||
@@ -49,7 +49,7 @@ mod tests {
|
|||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
app.is_loading = is_ready;
|
app.is_loading = is_ready;
|
||||||
app.data.lidarr_data.main_tabs.set_index(5);
|
app.data.lidarr_data.main_tabs.set_index(6);
|
||||||
|
|
||||||
SystemHandler::new(
|
SystemHandler::new(
|
||||||
DEFAULT_KEYBINDINGS.right.key,
|
DEFAULT_KEYBINDINGS.right.key,
|
||||||
|
|||||||
@@ -279,8 +279,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
|
|||||||
}
|
}
|
||||||
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt => {
|
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt => {
|
||||||
if self.app.data.sonarr_data.prompt_confirm {
|
if self.app.data.sonarr_data.prompt_confirm {
|
||||||
|
let (series_id, season_number) = self.extract_series_id_season_number_tuple();
|
||||||
self.app.data.sonarr_data.prompt_confirm_action = Some(
|
self.app.data.sonarr_data.prompt_confirm_action = Some(
|
||||||
SonarrEvent::TriggerAutomaticSeasonSearch(self.extract_series_id_season_number_tuple()),
|
SonarrEvent::TriggerAutomaticSeasonSearch(series_id, season_number),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,8 +405,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
|
|||||||
},
|
},
|
||||||
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt if matches_key!(confirm, key) => {
|
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt if matches_key!(confirm, key) => {
|
||||||
self.app.data.sonarr_data.prompt_confirm = true;
|
self.app.data.sonarr_data.prompt_confirm = true;
|
||||||
|
let (series_id, season_number) = self.extract_series_id_season_number_tuple();
|
||||||
self.app.data.sonarr_data.prompt_confirm_action = Some(
|
self.app.data.sonarr_data.prompt_confirm_action = Some(
|
||||||
SonarrEvent::TriggerAutomaticSeasonSearch(self.extract_series_id_season_number_tuple()),
|
SonarrEvent::TriggerAutomaticSeasonSearch(series_id, season_number),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ mod tests {
|
|||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(
|
#[case(
|
||||||
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
|
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
|
||||||
SonarrEvent::TriggerAutomaticSeasonSearch((0, 0))
|
SonarrEvent::TriggerAutomaticSeasonSearch(0, 0)
|
||||||
)]
|
)]
|
||||||
#[case(
|
#[case(
|
||||||
ActiveSonarrBlock::DeleteEpisodeFilePrompt,
|
ActiveSonarrBlock::DeleteEpisodeFilePrompt,
|
||||||
@@ -694,7 +694,7 @@ mod tests {
|
|||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(
|
#[case(
|
||||||
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
|
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
|
||||||
SonarrEvent::TriggerAutomaticSeasonSearch((0, 0))
|
SonarrEvent::TriggerAutomaticSeasonSearch(0, 0)
|
||||||
)]
|
)]
|
||||||
#[case(
|
#[case(
|
||||||
ActiveSonarrBlock::DeleteEpisodeFilePrompt,
|
ActiveSonarrBlock::DeleteEpisodeFilePrompt,
|
||||||
|
|||||||
@@ -278,8 +278,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
|
|||||||
}
|
}
|
||||||
_ if matches_key!(toggle_monitoring, key) => {
|
_ if matches_key!(toggle_monitoring, key) => {
|
||||||
self.app.data.sonarr_data.prompt_confirm = true;
|
self.app.data.sonarr_data.prompt_confirm = true;
|
||||||
|
let (series_id, season_number) = self.extract_series_id_season_number_tuple();
|
||||||
self.app.data.sonarr_data.prompt_confirm_action = Some(
|
self.app.data.sonarr_data.prompt_confirm_action = Some(
|
||||||
SonarrEvent::ToggleSeasonMonitoring(self.extract_series_id_season_number_tuple()),
|
SonarrEvent::ToggleSeasonMonitoring(series_id, season_number),
|
||||||
);
|
);
|
||||||
|
|
||||||
self
|
self
|
||||||
|
|||||||
@@ -378,7 +378,7 @@ mod tests {
|
|||||||
assert!(app.is_routing);
|
assert!(app.is_routing);
|
||||||
assert_some_eq_x!(
|
assert_some_eq_x!(
|
||||||
&app.data.sonarr_data.prompt_confirm_action,
|
&app.data.sonarr_data.prompt_confirm_action,
|
||||||
&SonarrEvent::ToggleSeasonMonitoring((0, 0))
|
&SonarrEvent::ToggleSeasonMonitoring(0, 0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -499,6 +499,28 @@ pub struct LidarrReleaseDownloadBody {
|
|||||||
pub indexer_id: i64,
|
pub indexer_id: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct BlocklistItem {
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub id: i64,
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub artist_id: i64,
|
||||||
|
pub album_ids: Option<Vec<Number>>,
|
||||||
|
pub source_title: String,
|
||||||
|
pub quality: QualityWrapper,
|
||||||
|
pub date: DateTime<Utc>,
|
||||||
|
pub protocol: String,
|
||||||
|
pub indexer: String,
|
||||||
|
pub message: String,
|
||||||
|
pub artist: Artist,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct BlocklistResponse {
|
||||||
|
pub records: Vec<BlocklistItem>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TrackFile {
|
pub struct TrackFile {
|
||||||
@@ -574,6 +596,7 @@ serde_enum_from!(
|
|||||||
Album(Album),
|
Album(Album),
|
||||||
Artist(Artist),
|
Artist(Artist),
|
||||||
Artists(Vec<Artist>),
|
Artists(Vec<Artist>),
|
||||||
|
BlocklistResponse(BlocklistResponse),
|
||||||
DiskSpaces(Vec<DiskSpace>),
|
DiskSpaces(Vec<DiskSpace>),
|
||||||
DownloadsResponse(DownloadsResponse),
|
DownloadsResponse(DownloadsResponse),
|
||||||
LidarrHistoryWrapper(LidarrHistoryWrapper),
|
LidarrHistoryWrapper(LidarrHistoryWrapper),
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ mod tests {
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::models::lidarr_models::{
|
use crate::models::lidarr_models::{
|
||||||
AddArtistSearchResult, Album, AudioTags, DownloadRecord, DownloadStatus, DownloadsResponse,
|
AddArtistSearchResult, Album, AudioTags, BlocklistItem, BlocklistResponse, DownloadRecord,
|
||||||
LidarrHistoryEventType, LidarrHistoryItem, LidarrHistoryWrapper, LidarrRelease, LidarrTask,
|
DownloadStatus, DownloadsResponse, LidarrHistoryEventType, LidarrHistoryItem,
|
||||||
MediaInfo, Member, MetadataProfile, MonitorType, NewItemMonitorType, SystemStatus, Track,
|
LidarrHistoryWrapper, LidarrRelease, LidarrTask, MediaInfo, Member, MetadataProfile,
|
||||||
TrackFile,
|
MonitorType, NewItemMonitorType, SystemStatus, Track, TrackFile,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_models::{
|
use crate::models::servarr_models::{
|
||||||
DiskSpace, HostConfig, Indexer, IndexerSettings, IndexerTestResult, Log, LogResponse,
|
DiskSpace, HostConfig, Indexer, IndexerSettings, IndexerTestResult, Log, LogResponse,
|
||||||
@@ -276,6 +276,23 @@ mod tests {
|
|||||||
assert_eq!(lidarr_serdeable, LidarrSerdeable::Artist(artist));
|
assert_eq!(lidarr_serdeable, LidarrSerdeable::Artist(artist));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_serdeable_from_blocklist_response() {
|
||||||
|
let blocklist_response = BlocklistResponse {
|
||||||
|
records: vec![BlocklistItem {
|
||||||
|
id: 1,
|
||||||
|
..BlocklistItem::default()
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let lidarr_serdeable: LidarrSerdeable = blocklist_response.clone().into();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
lidarr_serdeable,
|
||||||
|
LidarrSerdeable::BlocklistResponse(blocklist_response)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lidarr_serdeable_from_disk_spaces() {
|
fn test_lidarr_serdeable_from_disk_spaces() {
|
||||||
let disk_spaces = vec![DiskSpace {
|
let disk_spaces = vec![DiskSpace {
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ use serde_json::Number;
|
|||||||
|
|
||||||
use super::modals::{AddArtistModal, AddRootFolderModal, AlbumDetailsModal, EditArtistModal};
|
use super::modals::{AddArtistModal, AddRootFolderModal, AlbumDetailsModal, EditArtistModal};
|
||||||
use crate::app::context_clues::{
|
use crate::app::context_clues::{
|
||||||
DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
||||||
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
||||||
};
|
};
|
||||||
use crate::app::lidarr::lidarr_context_clues::{
|
use crate::app::lidarr::lidarr_context_clues::{
|
||||||
ARTIST_DETAILS_CONTEXT_CLUES, ARTIST_HISTORY_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
|
ARTIST_DETAILS_CONTEXT_CLUES, ARTIST_HISTORY_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
|
||||||
MANUAL_ARTIST_SEARCH_CONTEXT_CLUES,
|
MANUAL_ARTIST_SEARCH_CONTEXT_CLUES,
|
||||||
};
|
};
|
||||||
use crate::models::lidarr_models::{LidarrRelease, LidarrTask};
|
use crate::models::lidarr_models::{BlocklistItem, LidarrRelease, LidarrTask};
|
||||||
use crate::models::servarr_data::modals::EditIndexerModal;
|
use crate::models::servarr_data::modals::EditIndexerModal;
|
||||||
use crate::models::servarr_models::{IndexerSettings, QueueEvent};
|
use crate::models::servarr_models::{IndexerSettings, QueueEvent};
|
||||||
use crate::models::stateful_list::StatefulList;
|
use crate::models::stateful_list::StatefulList;
|
||||||
@@ -30,6 +30,7 @@ use {
|
|||||||
super::modals::TrackDetailsModal,
|
super::modals::TrackDetailsModal,
|
||||||
crate::models::lidarr_models::{MonitorType, NewItemMonitorType},
|
crate::models::lidarr_models::{MonitorType, NewItemMonitorType},
|
||||||
crate::models::stateful_table::SortOption,
|
crate::models::stateful_table::SortOption,
|
||||||
|
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::blocklist_item,
|
||||||
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::indexer_settings,
|
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::indexer_settings,
|
||||||
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::quality_profile_map,
|
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::quality_profile_map,
|
||||||
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{
|
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{
|
||||||
@@ -64,6 +65,7 @@ pub struct LidarrData<'a> {
|
|||||||
pub artist_history: StatefulTable<LidarrHistoryItem>,
|
pub artist_history: StatefulTable<LidarrHistoryItem>,
|
||||||
pub artist_info_tabs: TabState,
|
pub artist_info_tabs: TabState,
|
||||||
pub artists: StatefulTable<Artist>,
|
pub artists: StatefulTable<Artist>,
|
||||||
|
pub blocklist: StatefulTable<BlocklistItem>,
|
||||||
pub delete_files: bool,
|
pub delete_files: bool,
|
||||||
pub discography_releases: StatefulTable<LidarrRelease>,
|
pub discography_releases: StatefulTable<LidarrRelease>,
|
||||||
pub disk_space_vec: Vec<DiskSpace>,
|
pub disk_space_vec: Vec<DiskSpace>,
|
||||||
@@ -149,6 +151,7 @@ impl<'a> Default for LidarrData<'a> {
|
|||||||
album_details_modal: None,
|
album_details_modal: None,
|
||||||
artist_history: StatefulTable::default(),
|
artist_history: StatefulTable::default(),
|
||||||
artists: StatefulTable::default(),
|
artists: StatefulTable::default(),
|
||||||
|
blocklist: StatefulTable::default(),
|
||||||
delete_files: false,
|
delete_files: false,
|
||||||
discography_releases: StatefulTable::default(),
|
discography_releases: StatefulTable::default(),
|
||||||
disk_space_vec: Vec::new(),
|
disk_space_vec: Vec::new(),
|
||||||
@@ -187,6 +190,12 @@ impl<'a> Default for LidarrData<'a> {
|
|||||||
contextual_help: Some(&DOWNLOADS_CONTEXT_CLUES),
|
contextual_help: Some(&DOWNLOADS_CONTEXT_CLUES),
|
||||||
config: None,
|
config: None,
|
||||||
},
|
},
|
||||||
|
TabRoute {
|
||||||
|
title: "Blocklist".to_string(),
|
||||||
|
route: ActiveLidarrBlock::Blocklist.into(),
|
||||||
|
contextual_help: Some(&BLOCKLIST_CONTEXT_CLUES),
|
||||||
|
config: None,
|
||||||
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "History".to_string(),
|
title: "History".to_string(),
|
||||||
route: ActiveLidarrBlock::History.into(),
|
route: ActiveLidarrBlock::History.into(),
|
||||||
@@ -293,8 +302,10 @@ impl LidarrData<'_> {
|
|||||||
.metadata_profile_list
|
.metadata_profile_list
|
||||||
.set_items(vec![metadata_profile().name]);
|
.set_items(vec![metadata_profile().name]);
|
||||||
|
|
||||||
let mut track_details_modal = TrackDetailsModal::default();
|
let mut track_details_modal = TrackDetailsModal {
|
||||||
track_details_modal.track_details = ScrollableText::with_string("Some details".to_owned());
|
track_details: ScrollableText::with_string("Some details".to_owned()),
|
||||||
|
..TrackDetailsModal::default()
|
||||||
|
};
|
||||||
track_details_modal
|
track_details_modal
|
||||||
.track_history
|
.track_history
|
||||||
.set_items(vec![lidarr_history_item()]);
|
.set_items(vec![lidarr_history_item()]);
|
||||||
@@ -377,6 +388,8 @@ impl LidarrData<'_> {
|
|||||||
}]);
|
}]);
|
||||||
lidarr_data.artists.search = Some("artist search".into());
|
lidarr_data.artists.search = Some("artist search".into());
|
||||||
lidarr_data.artists.filter = Some("artist filter".into());
|
lidarr_data.artists.filter = Some("artist filter".into());
|
||||||
|
lidarr_data.blocklist.set_items(vec![blocklist_item()]);
|
||||||
|
lidarr_data.blocklist.sorting(vec![sort_option!(id)]);
|
||||||
lidarr_data.downloads.set_items(vec![download_record()]);
|
lidarr_data.downloads.set_items(vec![download_record()]);
|
||||||
lidarr_data.history.set_items(vec![lidarr_history_item()]);
|
lidarr_data.history.set_items(vec![lidarr_history_item()]);
|
||||||
lidarr_data.history.sorting(vec![SortOption {
|
lidarr_data.history.sorting(vec![SortOption {
|
||||||
@@ -444,6 +457,11 @@ pub enum ActiveLidarrBlock {
|
|||||||
AllIndexerSettingsPrompt,
|
AllIndexerSettingsPrompt,
|
||||||
AutomaticallySearchAlbumPrompt,
|
AutomaticallySearchAlbumPrompt,
|
||||||
AutomaticallySearchArtistPrompt,
|
AutomaticallySearchArtistPrompt,
|
||||||
|
Blocklist,
|
||||||
|
BlocklistItemDetails,
|
||||||
|
DeleteBlocklistItemPrompt,
|
||||||
|
BlocklistClearAllItemsPrompt,
|
||||||
|
BlocklistSortPrompt,
|
||||||
DeleteAlbumPrompt,
|
DeleteAlbumPrompt,
|
||||||
DeleteAlbumConfirmPrompt,
|
DeleteAlbumConfirmPrompt,
|
||||||
DeleteAlbumToggleDeleteFile,
|
DeleteAlbumToggleDeleteFile,
|
||||||
@@ -579,6 +597,14 @@ pub static ALBUM_DETAILS_BLOCKS: [ActiveLidarrBlock; 15] = [
|
|||||||
ActiveLidarrBlock::DeleteTrackFilePrompt,
|
ActiveLidarrBlock::DeleteTrackFilePrompt,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub static BLOCKLIST_BLOCKS: [ActiveLidarrBlock; 5] = [
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
ActiveLidarrBlock::BlocklistItemDetails,
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt,
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt,
|
||||||
|
ActiveLidarrBlock::BlocklistSortPrompt,
|
||||||
|
];
|
||||||
|
|
||||||
pub static DOWNLOADS_BLOCKS: [ActiveLidarrBlock; 3] = [
|
pub static DOWNLOADS_BLOCKS: [ActiveLidarrBlock; 3] = [
|
||||||
ActiveLidarrBlock::Downloads,
|
ActiveLidarrBlock::Downloads,
|
||||||
ActiveLidarrBlock::DeleteDownloadPrompt,
|
ActiveLidarrBlock::DeleteDownloadPrompt,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::app::context_clues::{
|
use crate::app::context_clues::{
|
||||||
DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES,
|
||||||
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
||||||
};
|
};
|
||||||
use crate::app::lidarr::lidarr_context_clues::{
|
use crate::app::lidarr::lidarr_context_clues::{
|
||||||
ARTIST_DETAILS_CONTEXT_CLUES, ARTIST_HISTORY_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
|
ARTIST_DETAILS_CONTEXT_CLUES, ARTIST_HISTORY_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
|
||||||
@@ -11,7 +11,7 @@ mod tests {
|
|||||||
use crate::models::lidarr_models::{Album, LidarrHistoryItem, LidarrRelease};
|
use crate::models::lidarr_models::{Album, LidarrHistoryItem, LidarrRelease};
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
ADD_ARTIST_BLOCKS, ADD_ARTIST_SELECTION_BLOCKS, ADD_ROOT_FOLDER_BLOCKS, ALBUM_DETAILS_BLOCKS,
|
ADD_ARTIST_BLOCKS, ADD_ARTIST_SELECTION_BLOCKS, ADD_ROOT_FOLDER_BLOCKS, ALBUM_DETAILS_BLOCKS,
|
||||||
ARTIST_DETAILS_BLOCKS, DELETE_ALBUM_BLOCKS, DELETE_ALBUM_SELECTION_BLOCKS,
|
ARTIST_DETAILS_BLOCKS, BLOCKLIST_BLOCKS, DELETE_ALBUM_BLOCKS, DELETE_ALBUM_SELECTION_BLOCKS,
|
||||||
DELETE_ARTIST_BLOCKS, DELETE_ARTIST_SELECTION_BLOCKS, DOWNLOADS_BLOCKS, EDIT_ARTIST_BLOCKS,
|
DELETE_ARTIST_BLOCKS, DELETE_ARTIST_SELECTION_BLOCKS, DOWNLOADS_BLOCKS, EDIT_ARTIST_BLOCKS,
|
||||||
EDIT_ARTIST_SELECTION_BLOCKS, EDIT_INDEXER_BLOCKS, EDIT_INDEXER_NZB_SELECTION_BLOCKS,
|
EDIT_ARTIST_SELECTION_BLOCKS, EDIT_INDEXER_BLOCKS, EDIT_INDEXER_NZB_SELECTION_BLOCKS,
|
||||||
EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, HISTORY_BLOCKS, INDEXER_SETTINGS_BLOCKS,
|
EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, HISTORY_BLOCKS, INDEXER_SETTINGS_BLOCKS,
|
||||||
@@ -149,6 +149,7 @@ mod tests {
|
|||||||
assert_none!(lidarr_data.album_details_modal);
|
assert_none!(lidarr_data.album_details_modal);
|
||||||
assert_is_empty!(lidarr_data.artists);
|
assert_is_empty!(lidarr_data.artists);
|
||||||
assert_is_empty!(lidarr_data.artist_history);
|
assert_is_empty!(lidarr_data.artist_history);
|
||||||
|
assert_is_empty!(lidarr_data.blocklist);
|
||||||
assert!(!lidarr_data.delete_files);
|
assert!(!lidarr_data.delete_files);
|
||||||
assert_is_empty!(lidarr_data.disk_space_vec);
|
assert_is_empty!(lidarr_data.disk_space_vec);
|
||||||
assert_is_empty!(lidarr_data.downloads);
|
assert_is_empty!(lidarr_data.downloads);
|
||||||
@@ -171,7 +172,7 @@ mod tests {
|
|||||||
assert_is_empty!(lidarr_data.updates);
|
assert_is_empty!(lidarr_data.updates);
|
||||||
assert_is_empty!(lidarr_data.version);
|
assert_is_empty!(lidarr_data.version);
|
||||||
|
|
||||||
assert_eq!(lidarr_data.main_tabs.tabs.len(), 6);
|
assert_eq!(lidarr_data.main_tabs.tabs.len(), 7);
|
||||||
|
|
||||||
assert_str_eq!(lidarr_data.main_tabs.tabs[0].title, "Library");
|
assert_str_eq!(lidarr_data.main_tabs.tabs[0].title, "Library");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -195,50 +196,61 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_none!(lidarr_data.main_tabs.tabs[1].config);
|
assert_none!(lidarr_data.main_tabs.tabs[1].config);
|
||||||
|
|
||||||
assert_str_eq!(lidarr_data.main_tabs.tabs[2].title, "History");
|
assert_str_eq!(lidarr_data.main_tabs.tabs[2].title, "Blocklist");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
lidarr_data.main_tabs.tabs[2].route,
|
lidarr_data.main_tabs.tabs[2].route,
|
||||||
ActiveLidarrBlock::History.into()
|
ActiveLidarrBlock::Blocklist.into()
|
||||||
);
|
);
|
||||||
assert_some_eq_x!(
|
assert_some_eq_x!(
|
||||||
&lidarr_data.main_tabs.tabs[2].contextual_help,
|
&lidarr_data.main_tabs.tabs[2].contextual_help,
|
||||||
&HISTORY_CONTEXT_CLUES
|
&BLOCKLIST_CONTEXT_CLUES
|
||||||
);
|
);
|
||||||
assert_none!(lidarr_data.main_tabs.tabs[2].config);
|
assert_none!(lidarr_data.main_tabs.tabs[2].config);
|
||||||
|
|
||||||
assert_str_eq!(lidarr_data.main_tabs.tabs[3].title, "Root Folders");
|
assert_str_eq!(lidarr_data.main_tabs.tabs[3].title, "History");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
lidarr_data.main_tabs.tabs[3].route,
|
lidarr_data.main_tabs.tabs[3].route,
|
||||||
ActiveLidarrBlock::RootFolders.into()
|
ActiveLidarrBlock::History.into()
|
||||||
);
|
);
|
||||||
assert_some_eq_x!(
|
assert_some_eq_x!(
|
||||||
&lidarr_data.main_tabs.tabs[3].contextual_help,
|
&lidarr_data.main_tabs.tabs[3].contextual_help,
|
||||||
&ROOT_FOLDERS_CONTEXT_CLUES
|
&HISTORY_CONTEXT_CLUES
|
||||||
);
|
);
|
||||||
assert_none!(lidarr_data.main_tabs.tabs[3].config);
|
assert_none!(lidarr_data.main_tabs.tabs[3].config);
|
||||||
|
|
||||||
assert_str_eq!(lidarr_data.main_tabs.tabs[4].title, "Indexers");
|
assert_str_eq!(lidarr_data.main_tabs.tabs[4].title, "Root Folders");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
lidarr_data.main_tabs.tabs[4].route,
|
lidarr_data.main_tabs.tabs[4].route,
|
||||||
ActiveLidarrBlock::Indexers.into()
|
ActiveLidarrBlock::RootFolders.into()
|
||||||
);
|
);
|
||||||
assert_some_eq_x!(
|
assert_some_eq_x!(
|
||||||
&lidarr_data.main_tabs.tabs[4].contextual_help,
|
&lidarr_data.main_tabs.tabs[4].contextual_help,
|
||||||
&INDEXERS_CONTEXT_CLUES
|
&ROOT_FOLDERS_CONTEXT_CLUES
|
||||||
);
|
);
|
||||||
assert_none!(lidarr_data.main_tabs.tabs[4].config);
|
assert_none!(lidarr_data.main_tabs.tabs[4].config);
|
||||||
|
|
||||||
assert_str_eq!(lidarr_data.main_tabs.tabs[5].title, "System");
|
assert_str_eq!(lidarr_data.main_tabs.tabs[5].title, "Indexers");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
lidarr_data.main_tabs.tabs[5].route,
|
lidarr_data.main_tabs.tabs[5].route,
|
||||||
ActiveLidarrBlock::System.into()
|
ActiveLidarrBlock::Indexers.into()
|
||||||
);
|
);
|
||||||
assert_some_eq_x!(
|
assert_some_eq_x!(
|
||||||
&lidarr_data.main_tabs.tabs[5].contextual_help,
|
&lidarr_data.main_tabs.tabs[5].contextual_help,
|
||||||
&SYSTEM_CONTEXT_CLUES
|
&INDEXERS_CONTEXT_CLUES
|
||||||
);
|
);
|
||||||
assert_none!(lidarr_data.main_tabs.tabs[5].config);
|
assert_none!(lidarr_data.main_tabs.tabs[5].config);
|
||||||
|
|
||||||
|
assert_str_eq!(lidarr_data.main_tabs.tabs[6].title, "System");
|
||||||
|
assert_eq!(
|
||||||
|
lidarr_data.main_tabs.tabs[6].route,
|
||||||
|
ActiveLidarrBlock::System.into()
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
&lidarr_data.main_tabs.tabs[6].contextual_help,
|
||||||
|
&SYSTEM_CONTEXT_CLUES
|
||||||
|
);
|
||||||
|
assert_none!(lidarr_data.main_tabs.tabs[6].config);
|
||||||
|
|
||||||
assert_eq!(lidarr_data.artist_info_tabs.tabs.len(), 3);
|
assert_eq!(lidarr_data.artist_info_tabs.tabs.len(), 3);
|
||||||
assert_str_eq!(lidarr_data.artist_info_tabs.tabs[0].title, "Albums");
|
assert_str_eq!(lidarr_data.artist_info_tabs.tabs[0].title, "Albums");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -326,6 +338,16 @@ mod tests {
|
|||||||
assert!(ALBUM_DETAILS_BLOCKS.contains(&ActiveLidarrBlock::DeleteTrackFilePrompt));
|
assert!(ALBUM_DETAILS_BLOCKS.contains(&ActiveLidarrBlock::DeleteTrackFilePrompt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_blocks_contents() {
|
||||||
|
assert_eq!(BLOCKLIST_BLOCKS.len(), 5);
|
||||||
|
assert!(BLOCKLIST_BLOCKS.contains(&ActiveLidarrBlock::Blocklist));
|
||||||
|
assert!(BLOCKLIST_BLOCKS.contains(&ActiveLidarrBlock::BlocklistItemDetails));
|
||||||
|
assert!(BLOCKLIST_BLOCKS.contains(&ActiveLidarrBlock::DeleteBlocklistItemPrompt));
|
||||||
|
assert!(BLOCKLIST_BLOCKS.contains(&ActiveLidarrBlock::BlocklistClearAllItemsPrompt));
|
||||||
|
assert!(BLOCKLIST_BLOCKS.contains(&ActiveLidarrBlock::BlocklistSortPrompt));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_downloads_blocks_contains_expected_blocks() {
|
fn test_downloads_blocks_contains_expected_blocks() {
|
||||||
assert_eq!(DOWNLOADS_BLOCKS.len(), 3);
|
assert_eq!(DOWNLOADS_BLOCKS.len(), 3);
|
||||||
|
|||||||
@@ -0,0 +1,353 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::models::lidarr_models::{Artist, BlocklistItem, BlocklistResponse, LidarrSerdeable};
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
|
use crate::models::stateful_table::SortOption;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{
|
||||||
|
artist, blocklist_item,
|
||||||
|
};
|
||||||
|
use crate::network::network_tests::test_utils::{MockServarrApi, test_network};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
use serde_json::{Number, json};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_clear_lidarr_blocklist_event() {
|
||||||
|
let blocklist_items = vec![
|
||||||
|
BlocklistItem {
|
||||||
|
id: 1,
|
||||||
|
..blocklist_item()
|
||||||
|
},
|
||||||
|
BlocklistItem {
|
||||||
|
id: 2,
|
||||||
|
..blocklist_item()
|
||||||
|
},
|
||||||
|
BlocklistItem {
|
||||||
|
id: 3,
|
||||||
|
..blocklist_item()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let expected_request_json = json!({ "ids": [1, 2, 3]});
|
||||||
|
let (mock, app, _server) = MockServarrApi::delete()
|
||||||
|
.with_request_body(expected_request_json)
|
||||||
|
.build_for(LidarrEvent::ClearBlocklist)
|
||||||
|
.await;
|
||||||
|
app
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.blocklist
|
||||||
|
.set_items(blocklist_items);
|
||||||
|
app.lock().await.server_tabs.set_index(2);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
network
|
||||||
|
.handle_lidarr_event(LidarrEvent::ClearBlocklist)
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
|
||||||
|
mock.assert_async().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_delete_lidarr_blocklist_item_event() {
|
||||||
|
let (mock, app, _server) = MockServarrApi::delete()
|
||||||
|
.path("/1")
|
||||||
|
.build_for(LidarrEvent::DeleteBlocklistItem(1))
|
||||||
|
.await;
|
||||||
|
app
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.blocklist
|
||||||
|
.set_items(vec![blocklist_item()]);
|
||||||
|
app.lock().await.server_tabs.set_index(2);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
network
|
||||||
|
.handle_lidarr_event(LidarrEvent::DeleteBlocklistItem(1))
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
|
||||||
|
mock.assert_async().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_lidarr_blocklist_event(#[values(true, false)] use_custom_sorting: bool) {
|
||||||
|
let blocklist_json = json!({"records": [{
|
||||||
|
"artistId": 1007,
|
||||||
|
"albumIds": [42020],
|
||||||
|
"sourceTitle": "z artist",
|
||||||
|
"quality": { "quality": { "name": "Lossless" }},
|
||||||
|
"date": "2023-05-20T21:29:16Z",
|
||||||
|
"protocol": "usenet",
|
||||||
|
"indexer": "NZBgeek (Prowlarr)",
|
||||||
|
"message": "test message",
|
||||||
|
"id": 123,
|
||||||
|
"artist": {
|
||||||
|
"id": 1,
|
||||||
|
"artistName": "Alex",
|
||||||
|
"foreignArtistId": "test-foreign-id",
|
||||||
|
"status": "continuing",
|
||||||
|
"overview": "some interesting description of the artist",
|
||||||
|
"artistType": "Person",
|
||||||
|
"disambiguation": "American pianist",
|
||||||
|
"path": "/nfs/music/test-artist",
|
||||||
|
"members": [{"name": "alex", "instrument": "piano"}],
|
||||||
|
"qualityProfileId": 1,
|
||||||
|
"metadataProfileId": 1,
|
||||||
|
"monitored": true,
|
||||||
|
"monitorNewItems": "all",
|
||||||
|
"genres": ["soundtrack"],
|
||||||
|
"tags": [1],
|
||||||
|
"added": "2023-01-01T00:00:00Z",
|
||||||
|
"ratings": { "votes": 15, "value": 8.4 },
|
||||||
|
"statistics": {
|
||||||
|
"albumCount": 1,
|
||||||
|
"trackFileCount": 15,
|
||||||
|
"trackCount": 15,
|
||||||
|
"totalTrackCount": 15,
|
||||||
|
"sizeOnDisk": 12345,
|
||||||
|
"percentOfTracks": 99.9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"artistId": 2001,
|
||||||
|
"artistTitle": "Test Artist",
|
||||||
|
"albumIds": [42018],
|
||||||
|
"sourceTitle": "A Artist",
|
||||||
|
"quality": { "quality": { "name": "Lossless" }},
|
||||||
|
"date": "2023-05-20T21:29:16Z",
|
||||||
|
"protocol": "usenet",
|
||||||
|
"indexer": "NZBgeek (Prowlarr)",
|
||||||
|
"message": "test message",
|
||||||
|
"id": 456,
|
||||||
|
"artist": {
|
||||||
|
"id": 1,
|
||||||
|
"artistName": "Alex",
|
||||||
|
"foreignArtistId": "test-foreign-id",
|
||||||
|
"status": "continuing",
|
||||||
|
"overview": "some interesting description of the artist",
|
||||||
|
"artistType": "Person",
|
||||||
|
"disambiguation": "American pianist",
|
||||||
|
"path": "/nfs/music/test-artist",
|
||||||
|
"members": [{"name": "alex", "instrument": "piano"}],
|
||||||
|
"qualityProfileId": 1,
|
||||||
|
"metadataProfileId": 1,
|
||||||
|
"monitored": true,
|
||||||
|
"monitorNewItems": "all",
|
||||||
|
"genres": ["soundtrack"],
|
||||||
|
"tags": [1],
|
||||||
|
"added": "2023-01-01T00:00:00Z",
|
||||||
|
"ratings": { "votes": 15, "value": 8.4 },
|
||||||
|
"statistics": {
|
||||||
|
"albumCount": 1,
|
||||||
|
"trackFileCount": 15,
|
||||||
|
"trackCount": 15,
|
||||||
|
"totalTrackCount": 15,
|
||||||
|
"sizeOnDisk": 12345,
|
||||||
|
"percentOfTracks": 99.9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]});
|
||||||
|
let response: BlocklistResponse = serde_json::from_value(blocklist_json.clone()).unwrap();
|
||||||
|
let mut expected_blocklist = vec![
|
||||||
|
BlocklistItem {
|
||||||
|
id: 123,
|
||||||
|
artist_id: 1007,
|
||||||
|
source_title: "z artist".into(),
|
||||||
|
album_ids: Some(vec![Number::from(42020)]),
|
||||||
|
..blocklist_item()
|
||||||
|
},
|
||||||
|
BlocklistItem {
|
||||||
|
id: 456,
|
||||||
|
artist_id: 2001,
|
||||||
|
source_title: "A Artist".into(),
|
||||||
|
album_ids: Some(vec![Number::from(42018)]),
|
||||||
|
..blocklist_item()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let (mock, app, _server) = MockServarrApi::get()
|
||||||
|
.returns(blocklist_json)
|
||||||
|
.build_for(LidarrEvent::GetBlocklist)
|
||||||
|
.await;
|
||||||
|
app
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.artists
|
||||||
|
.set_items(vec![Artist {
|
||||||
|
id: 1007,
|
||||||
|
artist_name: "Z Artist".into(),
|
||||||
|
..artist()
|
||||||
|
}]);
|
||||||
|
app.lock().await.data.lidarr_data.blocklist.sort_asc = true;
|
||||||
|
if use_custom_sorting {
|
||||||
|
let cmp_fn = |a: &BlocklistItem, b: &BlocklistItem| {
|
||||||
|
a.source_title
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.source_title.to_lowercase())
|
||||||
|
};
|
||||||
|
expected_blocklist.sort_by(cmp_fn);
|
||||||
|
|
||||||
|
let blocklist_sort_option = SortOption {
|
||||||
|
name: "Source Title",
|
||||||
|
cmp_fn: Some(cmp_fn),
|
||||||
|
};
|
||||||
|
app
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.blocklist
|
||||||
|
.sorting(vec![blocklist_sort_option]);
|
||||||
|
}
|
||||||
|
app.lock().await.server_tabs.set_index(2);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
let LidarrSerdeable::BlocklistResponse(blocklist) = network
|
||||||
|
.handle_lidarr_event(LidarrEvent::GetBlocklist)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
else {
|
||||||
|
panic!("Expected BlocklistResponse")
|
||||||
|
};
|
||||||
|
mock.assert_async().await;
|
||||||
|
assert_eq!(
|
||||||
|
app.lock().await.data.lidarr_data.blocklist.items,
|
||||||
|
expected_blocklist
|
||||||
|
);
|
||||||
|
assert!(app.lock().await.data.lidarr_data.blocklist.sort_asc);
|
||||||
|
assert_eq!(blocklist, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_lidarr_blocklist_event_no_op_when_user_is_selecting_sort_options() {
|
||||||
|
let blocklist_json = json!({"records": [{
|
||||||
|
"artistId": 1007,
|
||||||
|
"albumIds": [42020],
|
||||||
|
"sourceTitle": "z artist",
|
||||||
|
"quality": { "quality": { "name": "Lossless" }},
|
||||||
|
"date": "2023-05-20T21:29:16Z",
|
||||||
|
"protocol": "usenet",
|
||||||
|
"indexer": "NZBgeek (Prowlarr)",
|
||||||
|
"message": "test message",
|
||||||
|
"id": 123,
|
||||||
|
"artist": {
|
||||||
|
"id": 1,
|
||||||
|
"artistName": "Alex",
|
||||||
|
"foreignArtistId": "test-foreign-id",
|
||||||
|
"status": "continuing",
|
||||||
|
"overview": "some interesting description of the artist",
|
||||||
|
"artistType": "Person",
|
||||||
|
"disambiguation": "American pianist",
|
||||||
|
"path": "/nfs/music/test-artist",
|
||||||
|
"members": [{"name": "alex", "instrument": "piano"}],
|
||||||
|
"qualityProfileId": 1,
|
||||||
|
"metadataProfileId": 1,
|
||||||
|
"monitored": true,
|
||||||
|
"monitorNewItems": "all",
|
||||||
|
"genres": ["soundtrack"],
|
||||||
|
"tags": [1],
|
||||||
|
"added": "2023-01-01T00:00:00Z",
|
||||||
|
"ratings": { "votes": 15, "value": 8.4 },
|
||||||
|
"statistics": {
|
||||||
|
"albumCount": 1,
|
||||||
|
"trackFileCount": 15,
|
||||||
|
"trackCount": 15,
|
||||||
|
"totalTrackCount": 15,
|
||||||
|
"sizeOnDisk": 12345,
|
||||||
|
"percentOfTracks": 99.9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"artistId": 2001,
|
||||||
|
"albumIds": [42018],
|
||||||
|
"sourceTitle": "A Artist",
|
||||||
|
"quality": { "quality": { "name": "Lossless" }},
|
||||||
|
"date": "2023-05-20T21:29:16Z",
|
||||||
|
"protocol": "usenet",
|
||||||
|
"indexer": "NZBgeek (Prowlarr)",
|
||||||
|
"message": "test message",
|
||||||
|
"id": 456,
|
||||||
|
"artist": {
|
||||||
|
"id": 1,
|
||||||
|
"artistName": "Alex",
|
||||||
|
"foreignArtistId": "test-foreign-id",
|
||||||
|
"status": "continuing",
|
||||||
|
"overview": "some interesting description of the artist",
|
||||||
|
"artistType": "Person",
|
||||||
|
"disambiguation": "American pianist",
|
||||||
|
"path": "/nfs/music/test-artist",
|
||||||
|
"members": [{"name": "alex", "instrument": "piano"}],
|
||||||
|
"qualityProfileId": 1,
|
||||||
|
"metadataProfileId": 1,
|
||||||
|
"monitored": true,
|
||||||
|
"monitorNewItems": "all",
|
||||||
|
"genres": ["soundtrack"],
|
||||||
|
"tags": [1],
|
||||||
|
"added": "2023-01-01T00:00:00Z",
|
||||||
|
"ratings": { "votes": 15, "value": 8.4 },
|
||||||
|
"statistics": {
|
||||||
|
"albumCount": 1,
|
||||||
|
"trackFileCount": 15,
|
||||||
|
"trackCount": 15,
|
||||||
|
"totalTrackCount": 15,
|
||||||
|
"sizeOnDisk": 12345,
|
||||||
|
"percentOfTracks": 99.9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]});
|
||||||
|
let response: BlocklistResponse = serde_json::from_value(blocklist_json.clone()).unwrap();
|
||||||
|
let (mock, app, _server) = MockServarrApi::get()
|
||||||
|
.returns(blocklist_json)
|
||||||
|
.build_for(LidarrEvent::GetBlocklist)
|
||||||
|
.await;
|
||||||
|
app.lock().await.data.lidarr_data.blocklist.sort_asc = true;
|
||||||
|
app
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::BlocklistSortPrompt.into());
|
||||||
|
let cmp_fn = |a: &BlocklistItem, b: &BlocklistItem| {
|
||||||
|
a.source_title
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.source_title.to_lowercase())
|
||||||
|
};
|
||||||
|
let blocklist_sort_option = SortOption {
|
||||||
|
name: "Source Title",
|
||||||
|
cmp_fn: Some(cmp_fn),
|
||||||
|
};
|
||||||
|
app
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.blocklist
|
||||||
|
.sorting(vec![blocklist_sort_option]);
|
||||||
|
app.lock().await.server_tabs.set_index(2);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
let LidarrSerdeable::BlocklistResponse(blocklist) = network
|
||||||
|
.handle_lidarr_event(LidarrEvent::GetBlocklist)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
else {
|
||||||
|
panic!("Expected BlocklistResponse")
|
||||||
|
};
|
||||||
|
mock.assert_async().await;
|
||||||
|
assert_is_empty!(app.lock().await.data.lidarr_data.blocklist);
|
||||||
|
assert!(app.lock().await.data.lidarr_data.blocklist.sort_asc);
|
||||||
|
assert_eq!(blocklist, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
use crate::models::Route;
|
||||||
|
use crate::models::lidarr_models::{BlocklistItem, BlocklistResponse};
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::{Network, RequestMethod};
|
||||||
|
use anyhow::Result;
|
||||||
|
use log::info;
|
||||||
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "lidarr_blocklist_network_tests.rs"]
|
||||||
|
mod lidarr_blocklist_network_tests;
|
||||||
|
|
||||||
|
impl Network<'_, '_> {
|
||||||
|
pub(in crate::network::lidarr_network) async fn clear_lidarr_blocklist(&mut self) -> Result<()> {
|
||||||
|
info!("Clearing Lidarr blocklist");
|
||||||
|
let event = LidarrEvent::ClearBlocklist;
|
||||||
|
|
||||||
|
let ids = self
|
||||||
|
.app
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.blocklist
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.map(|item| item.id)
|
||||||
|
.collect::<Vec<i64>>();
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
event,
|
||||||
|
RequestMethod::Delete,
|
||||||
|
Some(json!({"ids": ids})),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<Value, ()>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::network::lidarr_network) async fn delete_lidarr_blocklist_item(
|
||||||
|
&mut self,
|
||||||
|
blocklist_item_id: i64,
|
||||||
|
) -> Result<()> {
|
||||||
|
let event = LidarrEvent::DeleteBlocklistItem(blocklist_item_id);
|
||||||
|
info!("Deleting Lidarr blocklist item for item with id: {blocklist_item_id}");
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
event,
|
||||||
|
RequestMethod::Delete,
|
||||||
|
None::<()>,
|
||||||
|
Some(format!("/{blocklist_item_id}")),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), ()>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::network::lidarr_network) async fn get_lidarr_blocklist(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<BlocklistResponse> {
|
||||||
|
info!("Fetching Lidarr blocklist");
|
||||||
|
let event = LidarrEvent::GetBlocklist;
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(event, RequestMethod::Get, None::<()>, None, None)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), BlocklistResponse>(request_props, |blocklist_resp, mut app| {
|
||||||
|
if !matches!(
|
||||||
|
app.get_current_route(),
|
||||||
|
Route::Lidarr(ActiveLidarrBlock::BlocklistSortPrompt, _)
|
||||||
|
) {
|
||||||
|
let mut blocklist_vec: Vec<BlocklistItem> = blocklist_resp.records;
|
||||||
|
blocklist_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec);
|
||||||
|
app.data.lidarr_data.blocklist.apply_sorting_toggle(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
pub mod test_utils {
|
pub mod test_utils {
|
||||||
use crate::models::lidarr_models::{
|
use crate::models::lidarr_models::{
|
||||||
AddArtistSearchResult, Album, AlbumStatistics, Artist, ArtistStatistics, ArtistStatus,
|
AddArtistSearchResult, Album, AlbumStatistics, Artist, ArtistStatistics, ArtistStatus,
|
||||||
AudioTags, DownloadRecord, DownloadStatus, DownloadsResponse, EditArtistParams,
|
AudioTags, BlocklistItem, BlocklistResponse, DownloadRecord, DownloadStatus, DownloadsResponse,
|
||||||
LidarrHistoryData, LidarrHistoryEventType, LidarrHistoryItem, LidarrHistoryWrapper,
|
EditArtistParams, LidarrHistoryData, LidarrHistoryEventType, LidarrHistoryItem,
|
||||||
LidarrRelease, LidarrTask, LidarrTaskName, MediaInfo, Member, MetadataProfile,
|
LidarrHistoryWrapper, LidarrRelease, LidarrTask, LidarrTaskName, MediaInfo, Member,
|
||||||
NewItemMonitorType, Ratings, SystemStatus, Track, TrackFile,
|
MetadataProfile, NewItemMonitorType, Ratings, SystemStatus, Track, TrackFile,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_models::IndexerSettings;
|
use crate::models::servarr_models::IndexerSettings;
|
||||||
use crate::models::servarr_models::{
|
use crate::models::servarr_models::{
|
||||||
@@ -477,4 +477,25 @@ pub mod test_utils {
|
|||||||
track_file: Some(track_file()),
|
track_file: Some(track_file()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn blocklist_item() -> BlocklistItem {
|
||||||
|
BlocklistItem {
|
||||||
|
id: 1,
|
||||||
|
artist_id: 1,
|
||||||
|
album_ids: Some(vec![1.into()]),
|
||||||
|
source_title: "Alex - Something".to_string(),
|
||||||
|
quality: quality_wrapper(),
|
||||||
|
date: DateTime::from(DateTime::parse_from_rfc3339("2023-05-20T21:29:16Z").unwrap()),
|
||||||
|
protocol: "usenet".to_string(),
|
||||||
|
indexer: "NZBgeek (Prowlarr)".to_string(),
|
||||||
|
message: "test message".to_string(),
|
||||||
|
artist: artist(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blocklist_response() -> BlocklistResponse {
|
||||||
|
BlocklistResponse {
|
||||||
|
records: vec![blocklist_item()],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,6 +159,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
|
#[case(LidarrEvent::ClearBlocklist, "/blocklist/bulk")]
|
||||||
|
#[case(LidarrEvent::DeleteBlocklistItem(0), "/blocklist")]
|
||||||
|
#[case(LidarrEvent::GetBlocklist, "/blocklist?page=1&pageSize=10000")]
|
||||||
#[case(LidarrEvent::GetDiskSpace, "/diskspace")]
|
#[case(LidarrEvent::GetDiskSpace, "/diskspace")]
|
||||||
#[case(LidarrEvent::GetMetadataProfiles, "/metadataprofile")]
|
#[case(LidarrEvent::GetMetadataProfiles, "/metadataprofile")]
|
||||||
#[case(LidarrEvent::GetQualityProfiles, "/qualityprofile")]
|
#[case(LidarrEvent::GetQualityProfiles, "/qualityprofile")]
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use crate::models::lidarr_models::{
|
|||||||
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};
|
||||||
|
|
||||||
|
mod blocklist;
|
||||||
mod downloads;
|
mod downloads;
|
||||||
mod history;
|
mod history;
|
||||||
mod indexers;
|
mod indexers;
|
||||||
@@ -29,8 +30,10 @@ pub enum LidarrEvent {
|
|||||||
AddArtist(AddArtistBody),
|
AddArtist(AddArtistBody),
|
||||||
AddRootFolder(AddLidarrRootFolderBody),
|
AddRootFolder(AddLidarrRootFolderBody),
|
||||||
AddTag(String),
|
AddTag(String),
|
||||||
|
ClearBlocklist,
|
||||||
DeleteAlbum(DeleteParams),
|
DeleteAlbum(DeleteParams),
|
||||||
DeleteArtist(DeleteParams),
|
DeleteArtist(DeleteParams),
|
||||||
|
DeleteBlocklistItem(i64),
|
||||||
DeleteDownload(i64),
|
DeleteDownload(i64),
|
||||||
DeleteIndexer(i64),
|
DeleteIndexer(i64),
|
||||||
DeleteRootFolder(i64),
|
DeleteRootFolder(i64),
|
||||||
@@ -47,6 +50,7 @@ pub enum LidarrEvent {
|
|||||||
GetArtistHistory(i64),
|
GetArtistHistory(i64),
|
||||||
GetAllIndexerSettings,
|
GetAllIndexerSettings,
|
||||||
GetArtistDetails(i64),
|
GetArtistDetails(i64),
|
||||||
|
GetBlocklist,
|
||||||
GetDiscographyReleases(i64),
|
GetDiscographyReleases(i64),
|
||||||
GetDiskSpace,
|
GetDiskSpace,
|
||||||
GetDownloads(u64),
|
GetDownloads(u64),
|
||||||
@@ -87,7 +91,9 @@ impl NetworkResource for LidarrEvent {
|
|||||||
fn resource(&self) -> &'static str {
|
fn resource(&self) -> &'static str {
|
||||||
match &self {
|
match &self {
|
||||||
LidarrEvent::AddTag(_) | LidarrEvent::DeleteTag(_) | LidarrEvent::GetTags => "/tag",
|
LidarrEvent::AddTag(_) | LidarrEvent::DeleteTag(_) | LidarrEvent::GetTags => "/tag",
|
||||||
|
LidarrEvent::ClearBlocklist => "/blocklist/bulk",
|
||||||
LidarrEvent::DeleteTrackFile(_) | LidarrEvent::GetTrackFiles(_) => "/trackfile",
|
LidarrEvent::DeleteTrackFile(_) | LidarrEvent::GetTrackFiles(_) => "/trackfile",
|
||||||
|
LidarrEvent::DeleteBlocklistItem(_) => "/blocklist",
|
||||||
LidarrEvent::GetAllIndexerSettings | LidarrEvent::EditAllIndexerSettings(_) => {
|
LidarrEvent::GetAllIndexerSettings | LidarrEvent::EditAllIndexerSettings(_) => {
|
||||||
"/config/indexer"
|
"/config/indexer"
|
||||||
}
|
}
|
||||||
@@ -104,6 +110,7 @@ impl NetworkResource for LidarrEvent {
|
|||||||
LidarrEvent::GetArtistHistory(_)
|
LidarrEvent::GetArtistHistory(_)
|
||||||
| LidarrEvent::GetAlbumHistory(_, _)
|
| LidarrEvent::GetAlbumHistory(_, _)
|
||||||
| LidarrEvent::GetTrackHistory(_, _, _) => "/history/artist",
|
| LidarrEvent::GetTrackHistory(_, _, _) => "/history/artist",
|
||||||
|
LidarrEvent::GetBlocklist => "/blocklist?page=1&pageSize=10000",
|
||||||
LidarrEvent::GetLogs(_) => "/log",
|
LidarrEvent::GetLogs(_) => "/log",
|
||||||
LidarrEvent::GetDiskSpace => "/diskspace",
|
LidarrEvent::GetDiskSpace => "/diskspace",
|
||||||
LidarrEvent::GetDownloads(_) | LidarrEvent::DeleteDownload(_) => "/queue",
|
LidarrEvent::GetDownloads(_) | LidarrEvent::DeleteDownload(_) => "/queue",
|
||||||
@@ -157,12 +164,20 @@ impl Network<'_, '_> {
|
|||||||
.add_lidarr_root_folder(path)
|
.add_lidarr_root_folder(path)
|
||||||
.await
|
.await
|
||||||
.map(LidarrSerdeable::from),
|
.map(LidarrSerdeable::from),
|
||||||
|
LidarrEvent::ClearBlocklist => self
|
||||||
|
.clear_lidarr_blocklist()
|
||||||
|
.await
|
||||||
|
.map(LidarrSerdeable::from),
|
||||||
LidarrEvent::DeleteAlbum(params) => {
|
LidarrEvent::DeleteAlbum(params) => {
|
||||||
self.delete_album(params).await.map(LidarrSerdeable::from)
|
self.delete_album(params).await.map(LidarrSerdeable::from)
|
||||||
}
|
}
|
||||||
LidarrEvent::DeleteArtist(params) => {
|
LidarrEvent::DeleteArtist(params) => {
|
||||||
self.delete_artist(params).await.map(LidarrSerdeable::from)
|
self.delete_artist(params).await.map(LidarrSerdeable::from)
|
||||||
}
|
}
|
||||||
|
LidarrEvent::DeleteBlocklistItem(blocklist_item_id) => self
|
||||||
|
.delete_lidarr_blocklist_item(blocklist_item_id)
|
||||||
|
.await
|
||||||
|
.map(LidarrSerdeable::from),
|
||||||
LidarrEvent::DeleteDownload(download_id) => self
|
LidarrEvent::DeleteDownload(download_id) => self
|
||||||
.delete_lidarr_download(download_id)
|
.delete_lidarr_download(download_id)
|
||||||
.await
|
.await
|
||||||
@@ -218,6 +233,7 @@ impl Network<'_, '_> {
|
|||||||
.get_album_releases(artist_id, album_id)
|
.get_album_releases(artist_id, album_id)
|
||||||
.await
|
.await
|
||||||
.map(LidarrSerdeable::from),
|
.map(LidarrSerdeable::from),
|
||||||
|
LidarrEvent::GetBlocklist => self.get_lidarr_blocklist().await.map(LidarrSerdeable::from),
|
||||||
LidarrEvent::GetDiscographyReleases(artist_id) => self
|
LidarrEvent::GetDiscographyReleases(artist_id) => self
|
||||||
.get_artist_discography_releases(artist_id)
|
.get_artist_discography_releases(artist_id)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ mod sonarr_seasons_network_tests;
|
|||||||
impl Network<'_, '_> {
|
impl Network<'_, '_> {
|
||||||
pub(in crate::network::sonarr_network) async fn toggle_sonarr_season_monitoring(
|
pub(in crate::network::sonarr_network) async fn toggle_sonarr_season_monitoring(
|
||||||
&mut self,
|
&mut self,
|
||||||
series_id_season_number_tuple: (i64, i64),
|
series_id: i64,
|
||||||
|
season_number: i64,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let event = SonarrEvent::ToggleSeasonMonitoring(series_id_season_number_tuple);
|
let event = SonarrEvent::ToggleSeasonMonitoring(series_id, season_number);
|
||||||
let (series_id, season_number) = series_id_season_number_tuple;
|
|
||||||
|
|
||||||
let detail_event = SonarrEvent::GetSeriesDetails(series_id);
|
let detail_event = SonarrEvent::GetSeriesDetails(series_id);
|
||||||
info!("Toggling season monitoring for season {season_number} in series with ID: {series_id}");
|
info!("Toggling season monitoring for season {season_number} in series with ID: {series_id}");
|
||||||
@@ -94,10 +94,10 @@ impl Network<'_, '_> {
|
|||||||
|
|
||||||
pub(in crate::network::sonarr_network) async fn get_season_releases(
|
pub(in crate::network::sonarr_network) async fn get_season_releases(
|
||||||
&mut self,
|
&mut self,
|
||||||
series_season_id_tuple: (i64, i64),
|
series_id: i64,
|
||||||
|
season_number: i64,
|
||||||
) -> Result<Vec<SonarrRelease>> {
|
) -> Result<Vec<SonarrRelease>> {
|
||||||
let event = SonarrEvent::GetSeasonReleases(series_season_id_tuple);
|
let event = SonarrEvent::GetSeasonReleases(series_id, season_number);
|
||||||
let (series_id, season_number) = series_season_id_tuple;
|
|
||||||
info!("Fetching releases for series with ID: {series_id} and season number: {season_number}");
|
info!("Fetching releases for series with ID: {series_id} and season number: {season_number}");
|
||||||
|
|
||||||
let request_props = self
|
let request_props = self
|
||||||
@@ -132,10 +132,10 @@ impl Network<'_, '_> {
|
|||||||
|
|
||||||
pub(in crate::network::sonarr_network) async fn get_sonarr_season_history(
|
pub(in crate::network::sonarr_network) async fn get_sonarr_season_history(
|
||||||
&mut self,
|
&mut self,
|
||||||
series_season_id_tuple: (i64, i64),
|
series_id: i64,
|
||||||
|
season_number: i64,
|
||||||
) -> Result<Vec<SonarrHistoryItem>> {
|
) -> Result<Vec<SonarrHistoryItem>> {
|
||||||
let event = SonarrEvent::GetSeasonHistory(series_season_id_tuple);
|
let event = SonarrEvent::GetSeasonHistory(series_id, season_number);
|
||||||
let (series_id, season_number) = series_season_id_tuple;
|
|
||||||
info!("Fetching history for series with ID: {series_id} and season number: {season_number}");
|
info!("Fetching history for series with ID: {series_id} and season number: {season_number}");
|
||||||
|
|
||||||
let params = format!("seriesId={series_id}&seasonNumber={season_number}",);
|
let params = format!("seriesId={series_id}&seasonNumber={season_number}",);
|
||||||
@@ -170,10 +170,10 @@ impl Network<'_, '_> {
|
|||||||
|
|
||||||
pub(in crate::network::sonarr_network) async fn trigger_automatic_season_search(
|
pub(in crate::network::sonarr_network) async fn trigger_automatic_season_search(
|
||||||
&mut self,
|
&mut self,
|
||||||
series_season_id_tuple: (i64, i64),
|
series_id: i64,
|
||||||
|
season_number: i64,
|
||||||
) -> Result<Value> {
|
) -> Result<Value> {
|
||||||
let event = SonarrEvent::TriggerAutomaticSeasonSearch(series_season_id_tuple);
|
let event = SonarrEvent::TriggerAutomaticSeasonSearch(series_id, season_number);
|
||||||
let (series_id, season_number) = series_season_id_tuple;
|
|
||||||
info!("Searching indexers for series with ID: {series_id} and season number: {season_number}");
|
info!("Searching indexers for series with ID: {series_id} and season number: {season_number}");
|
||||||
|
|
||||||
let body = SonarrCommandBody {
|
let body = SonarrCommandBody {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ mod tests {
|
|||||||
"PUT",
|
"PUT",
|
||||||
format!(
|
format!(
|
||||||
"/api/v3{}/1",
|
"/api/v3{}/1",
|
||||||
SonarrEvent::ToggleSeasonMonitoring((1, 1)).resource()
|
SonarrEvent::ToggleSeasonMonitoring(1, 1).resource()
|
||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
)
|
)
|
||||||
@@ -56,7 +56,7 @@ mod tests {
|
|||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
network
|
network
|
||||||
.handle_sonarr_event(SonarrEvent::ToggleSeasonMonitoring((1, 1)))
|
.handle_sonarr_event(SonarrEvent::ToggleSeasonMonitoring(1, 1))
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
);
|
);
|
||||||
@@ -117,7 +117,7 @@ mod tests {
|
|||||||
let (mock, app, _server) = MockServarrApi::get()
|
let (mock, app, _server) = MockServarrApi::get()
|
||||||
.returns(release_json)
|
.returns(release_json)
|
||||||
.query("seriesId=1&seasonNumber=1")
|
.query("seriesId=1&seasonNumber=1")
|
||||||
.build_for(SonarrEvent::GetSeasonReleases((1, 1)))
|
.build_for(SonarrEvent::GetSeasonReleases(1, 1))
|
||||||
.await;
|
.await;
|
||||||
app
|
app
|
||||||
.lock()
|
.lock()
|
||||||
@@ -138,7 +138,7 @@ mod tests {
|
|||||||
let mut network = test_network(&app);
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
let SonarrSerdeable::Releases(releases_vec) = network
|
let SonarrSerdeable::Releases(releases_vec) = network
|
||||||
.handle_sonarr_event(SonarrEvent::GetSeasonReleases((1, 1)))
|
.handle_sonarr_event(SonarrEvent::GetSeasonReleases(1, 1))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
else {
|
else {
|
||||||
@@ -203,7 +203,7 @@ mod tests {
|
|||||||
let (mock, app, _server) = MockServarrApi::get()
|
let (mock, app, _server) = MockServarrApi::get()
|
||||||
.returns(release_json)
|
.returns(release_json)
|
||||||
.query("seriesId=1&seasonNumber=1")
|
.query("seriesId=1&seasonNumber=1")
|
||||||
.build_for(SonarrEvent::GetSeasonReleases((1, 1)))
|
.build_for(SonarrEvent::GetSeasonReleases(1, 1))
|
||||||
.await;
|
.await;
|
||||||
app
|
app
|
||||||
.lock()
|
.lock()
|
||||||
@@ -224,7 +224,7 @@ mod tests {
|
|||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
network
|
network
|
||||||
.handle_sonarr_event(SonarrEvent::GetSeasonReleases((1, 1)))
|
.handle_sonarr_event(SonarrEvent::GetSeasonReleases(1, 1))
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
);
|
);
|
||||||
@@ -291,7 +291,7 @@ mod tests {
|
|||||||
let (mock, app, _server) = MockServarrApi::get()
|
let (mock, app, _server) = MockServarrApi::get()
|
||||||
.returns(history_json)
|
.returns(history_json)
|
||||||
.query("seriesId=1&seasonNumber=1")
|
.query("seriesId=1&seasonNumber=1")
|
||||||
.build_for(SonarrEvent::GetSeasonHistory((1, 1)))
|
.build_for(SonarrEvent::GetSeasonHistory(1, 1))
|
||||||
.await;
|
.await;
|
||||||
app.lock().await.data.sonarr_data.season_details_modal = Some(SeasonDetailsModal::default());
|
app.lock().await.data.sonarr_data.season_details_modal = Some(SeasonDetailsModal::default());
|
||||||
app
|
app
|
||||||
@@ -322,7 +322,7 @@ mod tests {
|
|||||||
let mut network = test_network(&app);
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
let SonarrSerdeable::SonarrHistoryItems(history) = network
|
let SonarrSerdeable::SonarrHistoryItems(history) = network
|
||||||
.handle_sonarr_event(SonarrEvent::GetSeasonHistory((1, 1)))
|
.handle_sonarr_event(SonarrEvent::GetSeasonHistory(1, 1))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
else {
|
else {
|
||||||
@@ -403,7 +403,7 @@ mod tests {
|
|||||||
let (mock, app, _server) = MockServarrApi::get()
|
let (mock, app, _server) = MockServarrApi::get()
|
||||||
.returns(history_json)
|
.returns(history_json)
|
||||||
.query("seriesId=1&seasonNumber=1")
|
.query("seriesId=1&seasonNumber=1")
|
||||||
.build_for(SonarrEvent::GetSeasonHistory((1, 1)))
|
.build_for(SonarrEvent::GetSeasonHistory(1, 1))
|
||||||
.await;
|
.await;
|
||||||
app
|
app
|
||||||
.lock()
|
.lock()
|
||||||
@@ -423,7 +423,7 @@ mod tests {
|
|||||||
let mut network = test_network(&app);
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
let SonarrSerdeable::SonarrHistoryItems(history) = network
|
let SonarrSerdeable::SonarrHistoryItems(history) = network
|
||||||
.handle_sonarr_event(SonarrEvent::GetSeasonHistory((1, 1)))
|
.handle_sonarr_event(SonarrEvent::GetSeasonHistory(1, 1))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
else {
|
else {
|
||||||
@@ -499,7 +499,7 @@ mod tests {
|
|||||||
let (mock, app, _server) = MockServarrApi::get()
|
let (mock, app, _server) = MockServarrApi::get()
|
||||||
.returns(history_json)
|
.returns(history_json)
|
||||||
.query("seriesId=1&seasonNumber=1")
|
.query("seriesId=1&seasonNumber=1")
|
||||||
.build_for(SonarrEvent::GetSeasonHistory((1, 1)))
|
.build_for(SonarrEvent::GetSeasonHistory(1, 1))
|
||||||
.await;
|
.await;
|
||||||
app.lock().await.data.sonarr_data.season_details_modal = Some(SeasonDetailsModal::default());
|
app.lock().await.data.sonarr_data.season_details_modal = Some(SeasonDetailsModal::default());
|
||||||
app
|
app
|
||||||
@@ -520,7 +520,7 @@ mod tests {
|
|||||||
let mut network = test_network(&app);
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
let SonarrSerdeable::SonarrHistoryItems(history) = network
|
let SonarrSerdeable::SonarrHistoryItems(history) = network
|
||||||
.handle_sonarr_event(SonarrEvent::GetSeasonHistory((1, 1)))
|
.handle_sonarr_event(SonarrEvent::GetSeasonHistory(1, 1))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
else {
|
else {
|
||||||
@@ -563,14 +563,14 @@ mod tests {
|
|||||||
"seasonNumber": 1
|
"seasonNumber": 1
|
||||||
}))
|
}))
|
||||||
.returns(json!({}))
|
.returns(json!({}))
|
||||||
.build_for(SonarrEvent::TriggerAutomaticSeasonSearch((1, 1)))
|
.build_for(SonarrEvent::TriggerAutomaticSeasonSearch(1, 1))
|
||||||
.await;
|
.await;
|
||||||
app.lock().await.server_tabs.next();
|
app.lock().await.server_tabs.next();
|
||||||
let mut network = test_network(&app);
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
network
|
network
|
||||||
.handle_sonarr_event(SonarrEvent::TriggerAutomaticSeasonSearch((1, 1)))
|
.handle_sonarr_event(SonarrEvent::TriggerAutomaticSeasonSearch(1, 1))
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ pub enum SonarrEvent {
|
|||||||
GetQueuedEvents,
|
GetQueuedEvents,
|
||||||
GetRootFolders,
|
GetRootFolders,
|
||||||
GetEpisodeReleases(i64),
|
GetEpisodeReleases(i64),
|
||||||
GetSeasonHistory((i64, i64)),
|
GetSeasonHistory(i64, i64),
|
||||||
GetSeasonReleases((i64, i64)),
|
GetSeasonReleases(i64, i64),
|
||||||
GetSecurityConfig,
|
GetSecurityConfig,
|
||||||
GetSeriesDetails(i64),
|
GetSeriesDetails(i64),
|
||||||
GetSeriesHistory(i64),
|
GetSeriesHistory(i64),
|
||||||
@@ -81,11 +81,11 @@ pub enum SonarrEvent {
|
|||||||
StartTask(SonarrTaskName),
|
StartTask(SonarrTaskName),
|
||||||
TestIndexer(i64),
|
TestIndexer(i64),
|
||||||
TestAllIndexers,
|
TestAllIndexers,
|
||||||
ToggleSeasonMonitoring((i64, i64)),
|
ToggleSeasonMonitoring(i64, i64),
|
||||||
ToggleSeriesMonitoring(i64),
|
ToggleSeriesMonitoring(i64),
|
||||||
ToggleEpisodeMonitoring(i64),
|
ToggleEpisodeMonitoring(i64),
|
||||||
TriggerAutomaticEpisodeSearch(i64),
|
TriggerAutomaticEpisodeSearch(i64),
|
||||||
TriggerAutomaticSeasonSearch((i64, i64)),
|
TriggerAutomaticSeasonSearch(i64, i64),
|
||||||
TriggerAutomaticSeriesSearch(i64),
|
TriggerAutomaticSeriesSearch(i64),
|
||||||
UpdateAllSeries,
|
UpdateAllSeries,
|
||||||
UpdateAndScanSeries(i64),
|
UpdateAndScanSeries(i64),
|
||||||
@@ -118,7 +118,7 @@ impl NetworkResource for SonarrEvent {
|
|||||||
SonarrEvent::GetQueuedEvents
|
SonarrEvent::GetQueuedEvents
|
||||||
| SonarrEvent::StartTask(_)
|
| SonarrEvent::StartTask(_)
|
||||||
| SonarrEvent::TriggerAutomaticSeriesSearch(_)
|
| SonarrEvent::TriggerAutomaticSeriesSearch(_)
|
||||||
| SonarrEvent::TriggerAutomaticSeasonSearch(_)
|
| SonarrEvent::TriggerAutomaticSeasonSearch(_, _)
|
||||||
| SonarrEvent::TriggerAutomaticEpisodeSearch(_)
|
| SonarrEvent::TriggerAutomaticEpisodeSearch(_)
|
||||||
| SonarrEvent::UpdateAllSeries
|
| SonarrEvent::UpdateAllSeries
|
||||||
| SonarrEvent::UpdateAndScanSeries(_)
|
| SonarrEvent::UpdateAndScanSeries(_)
|
||||||
@@ -126,8 +126,8 @@ impl NetworkResource for SonarrEvent {
|
|||||||
SonarrEvent::GetRootFolders
|
SonarrEvent::GetRootFolders
|
||||||
| SonarrEvent::DeleteRootFolder(_)
|
| SonarrEvent::DeleteRootFolder(_)
|
||||||
| SonarrEvent::AddRootFolder(_) => "/rootfolder",
|
| SonarrEvent::AddRootFolder(_) => "/rootfolder",
|
||||||
SonarrEvent::GetSeasonReleases(_) | SonarrEvent::GetEpisodeReleases(_) => "/release",
|
SonarrEvent::GetSeasonReleases(_, _) | SonarrEvent::GetEpisodeReleases(_) => "/release",
|
||||||
SonarrEvent::GetSeriesHistory(_) | SonarrEvent::GetSeasonHistory(_) => "/history/series",
|
SonarrEvent::GetSeriesHistory(_) | SonarrEvent::GetSeasonHistory(_, _) => "/history/series",
|
||||||
SonarrEvent::GetStatus => "/system/status",
|
SonarrEvent::GetStatus => "/system/status",
|
||||||
SonarrEvent::GetTasks => "/system/task",
|
SonarrEvent::GetTasks => "/system/task",
|
||||||
SonarrEvent::GetUpdates => "/update",
|
SonarrEvent::GetUpdates => "/update",
|
||||||
@@ -137,7 +137,7 @@ impl NetworkResource for SonarrEvent {
|
|||||||
| SonarrEvent::GetSeriesDetails(_)
|
| SonarrEvent::GetSeriesDetails(_)
|
||||||
| SonarrEvent::DeleteSeries(_)
|
| SonarrEvent::DeleteSeries(_)
|
||||||
| SonarrEvent::EditSeries(_)
|
| SonarrEvent::EditSeries(_)
|
||||||
| SonarrEvent::ToggleSeasonMonitoring(_)
|
| SonarrEvent::ToggleSeasonMonitoring(_, _)
|
||||||
| SonarrEvent::ToggleSeriesMonitoring(_) => "/series",
|
| SonarrEvent::ToggleSeriesMonitoring(_) => "/series",
|
||||||
SonarrEvent::SearchNewSeries(_) => "/series/lookup",
|
SonarrEvent::SearchNewSeries(_) => "/series/lookup",
|
||||||
SonarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed",
|
SonarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed",
|
||||||
@@ -275,12 +275,12 @@ impl Network<'_, '_> {
|
|||||||
.get_episode_releases(params)
|
.get_episode_releases(params)
|
||||||
.await
|
.await
|
||||||
.map(SonarrSerdeable::from),
|
.map(SonarrSerdeable::from),
|
||||||
SonarrEvent::GetSeasonHistory(params) => self
|
SonarrEvent::GetSeasonHistory(series_id, season_number) => self
|
||||||
.get_sonarr_season_history(params)
|
.get_sonarr_season_history(series_id, season_number)
|
||||||
.await
|
.await
|
||||||
.map(SonarrSerdeable::from),
|
.map(SonarrSerdeable::from),
|
||||||
SonarrEvent::GetSeasonReleases(params) => self
|
SonarrEvent::GetSeasonReleases(series_id, season_number) => self
|
||||||
.get_season_releases(params)
|
.get_season_releases(series_id, season_number)
|
||||||
.await
|
.await
|
||||||
.map(SonarrSerdeable::from),
|
.map(SonarrSerdeable::from),
|
||||||
SonarrEvent::GetSecurityConfig => self
|
SonarrEvent::GetSecurityConfig => self
|
||||||
@@ -328,16 +328,16 @@ impl Network<'_, '_> {
|
|||||||
.toggle_sonarr_episode_monitoring(episode_id)
|
.toggle_sonarr_episode_monitoring(episode_id)
|
||||||
.await
|
.await
|
||||||
.map(SonarrSerdeable::from),
|
.map(SonarrSerdeable::from),
|
||||||
SonarrEvent::ToggleSeasonMonitoring(params) => self
|
SonarrEvent::ToggleSeasonMonitoring(series_id, season_number) => self
|
||||||
.toggle_sonarr_season_monitoring(params)
|
.toggle_sonarr_season_monitoring(series_id, season_number)
|
||||||
.await
|
.await
|
||||||
.map(SonarrSerdeable::from),
|
.map(SonarrSerdeable::from),
|
||||||
SonarrEvent::ToggleSeriesMonitoring(series_id) => self
|
SonarrEvent::ToggleSeriesMonitoring(series_id) => self
|
||||||
.toggle_sonarr_series_monitoring(series_id)
|
.toggle_sonarr_series_monitoring(series_id)
|
||||||
.await
|
.await
|
||||||
.map(SonarrSerdeable::from),
|
.map(SonarrSerdeable::from),
|
||||||
SonarrEvent::TriggerAutomaticSeasonSearch(params) => self
|
SonarrEvent::TriggerAutomaticSeasonSearch(series_id, season_number) => self
|
||||||
.trigger_automatic_season_search(params)
|
.trigger_automatic_season_search(series_id, season_number)
|
||||||
.await
|
.await
|
||||||
.map(SonarrSerdeable::from),
|
.map(SonarrSerdeable::from),
|
||||||
SonarrEvent::TriggerAutomaticSeriesSearch(series_id) => self
|
SonarrEvent::TriggerAutomaticSeriesSearch(series_id) => self
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ mod test {
|
|||||||
SonarrEvent::GetSeriesDetails(0),
|
SonarrEvent::GetSeriesDetails(0),
|
||||||
SonarrEvent::DeleteSeries(DeleteSeriesParams::default()),
|
SonarrEvent::DeleteSeries(DeleteSeriesParams::default()),
|
||||||
SonarrEvent::EditSeries(EditSeriesParams::default()),
|
SonarrEvent::EditSeries(EditSeriesParams::default()),
|
||||||
SonarrEvent::ToggleSeasonMonitoring((0, 0)),
|
SonarrEvent::ToggleSeasonMonitoring(0, 0),
|
||||||
SonarrEvent::ToggleSeriesMonitoring(0),
|
SonarrEvent::ToggleSeriesMonitoring(0)
|
||||||
)]
|
)]
|
||||||
event: SonarrEvent,
|
event: SonarrEvent,
|
||||||
) {
|
) {
|
||||||
@@ -76,7 +76,7 @@ mod test {
|
|||||||
SonarrEvent::GetQueuedEvents,
|
SonarrEvent::GetQueuedEvents,
|
||||||
SonarrEvent::StartTask(SonarrTaskName::default()),
|
SonarrEvent::StartTask(SonarrTaskName::default()),
|
||||||
SonarrEvent::TriggerAutomaticEpisodeSearch(0),
|
SonarrEvent::TriggerAutomaticEpisodeSearch(0),
|
||||||
SonarrEvent::TriggerAutomaticSeasonSearch((0, 0)),
|
SonarrEvent::TriggerAutomaticSeasonSearch(0, 0),
|
||||||
SonarrEvent::TriggerAutomaticSeriesSearch(0),
|
SonarrEvent::TriggerAutomaticSeriesSearch(0),
|
||||||
SonarrEvent::UpdateAllSeries,
|
SonarrEvent::UpdateAllSeries,
|
||||||
SonarrEvent::UpdateAndScanSeries(0),
|
SonarrEvent::UpdateAndScanSeries(0),
|
||||||
@@ -108,10 +108,7 @@ mod test {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_resource_series_history(
|
fn test_resource_series_history(
|
||||||
#[values(
|
#[values(SonarrEvent::GetSeriesHistory(0), SonarrEvent::GetSeasonHistory(0, 0))]
|
||||||
SonarrEvent::GetSeriesHistory(0),
|
|
||||||
SonarrEvent::GetSeasonHistory((0, 0))
|
|
||||||
)]
|
|
||||||
event: SonarrEvent,
|
event: SonarrEvent,
|
||||||
) {
|
) {
|
||||||
assert_str_eq!(event.resource(), "/history/series");
|
assert_str_eq!(event.resource(), "/history/series");
|
||||||
@@ -139,7 +136,7 @@ mod test {
|
|||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_resource_release(
|
fn test_resource_release(
|
||||||
#[values(
|
#[values(
|
||||||
SonarrEvent::GetSeasonReleases((0, 0)),
|
SonarrEvent::GetSeasonReleases(0, 0),
|
||||||
SonarrEvent::GetEpisodeReleases(0)
|
SonarrEvent::GetEpisodeReleases(0)
|
||||||
)]
|
)]
|
||||||
event: SonarrEvent,
|
event: SonarrEvent,
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, BLOCKLIST_BLOCKS};
|
||||||
|
use crate::ui::DrawUi;
|
||||||
|
use crate::ui::lidarr_ui::blocklist::BlocklistUi;
|
||||||
|
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_ui_accepts() {
|
||||||
|
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
||||||
|
if BLOCKLIST_BLOCKS.contains(&active_lidarr_block) {
|
||||||
|
assert!(BlocklistUi::accepts(active_lidarr_block.into()));
|
||||||
|
} else {
|
||||||
|
assert!(!BlocklistUi::accepts(active_lidarr_block.into()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mod snapshot_tests {
|
||||||
|
use crate::ui::ui_test_utils::test_utils::TerminalSize;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_ui_renders_loading() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
BlocklistUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_ui_renders_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
BlocklistUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_blocklist_ui_renders(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
ActiveLidarrBlock::BlocklistItemDetails,
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt,
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt,
|
||||||
|
ActiveLidarrBlock::BlocklistSortPrompt
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
BlocklistUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(format!("blocklist_tab_{active_lidarr_block}"), output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
use crate::app::App;
|
||||||
|
use crate::models::Route;
|
||||||
|
use crate::models::lidarr_models::BlocklistItem;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, BLOCKLIST_BLOCKS};
|
||||||
|
use crate::ui::DrawUi;
|
||||||
|
use crate::ui::styles::{ManagarrStyle, secondary_style};
|
||||||
|
use crate::ui::utils::layout_block_top_border;
|
||||||
|
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
||||||
|
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||||
|
use crate::ui::widgets::message::Message;
|
||||||
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
|
use ratatui::Frame;
|
||||||
|
use ratatui::layout::{Alignment, Constraint, Rect};
|
||||||
|
use ratatui::style::Stylize;
|
||||||
|
use ratatui::text::{Line, Text};
|
||||||
|
use ratatui::widgets::{Cell, Row};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "blocklist_ui_tests.rs"]
|
||||||
|
mod blocklist_ui_tests;
|
||||||
|
|
||||||
|
pub(super) struct BlocklistUi;
|
||||||
|
|
||||||
|
impl DrawUi for BlocklistUi {
|
||||||
|
fn accepts(route: Route) -> bool {
|
||||||
|
if let Route::Lidarr(active_lidarr_block, _) = route {
|
||||||
|
return BLOCKLIST_BLOCKS.contains(&active_lidarr_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
|
if let Route::Lidarr(active_lidarr_block, _) = app.get_current_route() {
|
||||||
|
draw_blocklist_table(f, app, area);
|
||||||
|
|
||||||
|
match active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::BlocklistItemDetails => {
|
||||||
|
draw_blocklist_item_details_popup(f, app);
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt => {
|
||||||
|
let prompt = format!(
|
||||||
|
"Do you want to remove this item from your blocklist: \n{}?",
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.blocklist
|
||||||
|
.current_selection()
|
||||||
|
.source_title
|
||||||
|
);
|
||||||
|
let confirmation_prompt = ConfirmationPrompt::new()
|
||||||
|
.title("Remove Item from Blocklist")
|
||||||
|
.prompt(&prompt)
|
||||||
|
.yes_no_value(app.data.lidarr_data.prompt_confirm);
|
||||||
|
|
||||||
|
f.render_widget(
|
||||||
|
Popup::new(confirmation_prompt).size(Size::MediumPrompt),
|
||||||
|
f.area(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt => {
|
||||||
|
let confirmation_prompt = ConfirmationPrompt::new()
|
||||||
|
.title("Clear Blocklist")
|
||||||
|
.prompt("Do you want to clear your blocklist?")
|
||||||
|
.yes_no_value(app.data.lidarr_data.prompt_confirm);
|
||||||
|
|
||||||
|
f.render_widget(
|
||||||
|
Popup::new(confirmation_prompt).size(Size::SmallPrompt),
|
||||||
|
f.area(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_blocklist_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
|
if let Route::Lidarr(active_lidarr_block, _) = app.get_current_route() {
|
||||||
|
let blocklist_row_mapping = |blocklist_item: &BlocklistItem| {
|
||||||
|
let BlocklistItem {
|
||||||
|
source_title,
|
||||||
|
artist,
|
||||||
|
quality,
|
||||||
|
date,
|
||||||
|
..
|
||||||
|
} = blocklist_item;
|
||||||
|
|
||||||
|
let title = artist.artist_name.text.to_owned();
|
||||||
|
|
||||||
|
Row::new(vec![
|
||||||
|
Cell::from(title),
|
||||||
|
Cell::from(source_title.to_owned()),
|
||||||
|
Cell::from(quality.quality.name.to_owned()),
|
||||||
|
Cell::from(date.to_string()),
|
||||||
|
])
|
||||||
|
.primary()
|
||||||
|
};
|
||||||
|
let blocklist_table = ManagarrTable::new(
|
||||||
|
Some(&mut app.data.lidarr_data.blocklist),
|
||||||
|
blocklist_row_mapping,
|
||||||
|
)
|
||||||
|
.block(layout_block_top_border())
|
||||||
|
.loading(app.is_loading)
|
||||||
|
.sorting(active_lidarr_block == ActiveLidarrBlock::BlocklistSortPrompt)
|
||||||
|
.headers(["Artist Name", "Source Title", "Quality", "Date"])
|
||||||
|
.constraints([
|
||||||
|
Constraint::Percentage(27),
|
||||||
|
Constraint::Percentage(43),
|
||||||
|
Constraint::Percentage(13),
|
||||||
|
Constraint::Percentage(17),
|
||||||
|
]);
|
||||||
|
|
||||||
|
f.render_widget(blocklist_table, area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_blocklist_item_details_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||||
|
let current_selection = if app.data.lidarr_data.blocklist.items.is_empty() {
|
||||||
|
BlocklistItem::default()
|
||||||
|
} else {
|
||||||
|
app.data.lidarr_data.blocklist.current_selection().clone()
|
||||||
|
};
|
||||||
|
let BlocklistItem {
|
||||||
|
source_title,
|
||||||
|
protocol,
|
||||||
|
indexer,
|
||||||
|
message,
|
||||||
|
..
|
||||||
|
} = current_selection;
|
||||||
|
let text = Text::from(vec![
|
||||||
|
Line::from(vec![
|
||||||
|
"Name: ".bold().secondary(),
|
||||||
|
source_title.to_owned().secondary(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Protocol: ".bold().secondary(),
|
||||||
|
protocol.to_owned().secondary(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Indexer: ".bold().secondary(),
|
||||||
|
indexer.to_owned().secondary(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Message: ".bold().secondary(),
|
||||||
|
message.to_owned().secondary(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let message = Message::new(text)
|
||||||
|
.title("Details")
|
||||||
|
.style(secondary_style())
|
||||||
|
.alignment(Alignment::Left);
|
||||||
|
|
||||||
|
f.render_widget(Popup::new(message).size(Size::NarrowMessage), f.area());
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/blocklist/blocklist_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
Artist Name ▼ Source Title Quality Date
|
||||||
|
=> Alex Alex - Something Lossless 2023-05-20 21:29:16 UTC
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/blocklist/blocklist_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
Artist Name ▼ Source Title Quality Date
|
||||||
|
=> Alex Alex - Something Lossless 2023-05-20 21:29:16 UTC
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭────── Clear Blocklist ──────╮
|
||||||
|
│ Do you want to clear your │
|
||||||
|
│ blocklist? │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│╭──────────────╮╭─────────────╮│
|
||||||
|
││ Yes ││ No ││
|
||||||
|
│╰──────────────╯╰─────────────╯│
|
||||||
|
╰───────────────────────────────╯
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/blocklist/blocklist_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
Artist Name ▼ Source Title Quality Date
|
||||||
|
=> Alex Alex - Something Lossless 2023-05-20 21:29:16 UTC
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭─────────────────────────────────── Details ───────────────────────────────────╮
|
||||||
|
│Name: Alex - Something │
|
||||||
|
│Protocol: usenet │
|
||||||
|
│Indexer: NZBgeek (Prowlarr) │
|
||||||
|
│Message: test message │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
╰─────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/blocklist/blocklist_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
Artist Name Source Title Quality Date
|
||||||
|
=> Alex Alex - Something Lossless 2023-05-20 21:29:16 UTC
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭───────────────────────────────╮
|
||||||
|
│Something │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
╰───────────────────────────────╯
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/blocklist/blocklist_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
Artist Name ▼ Source Title Quality Date
|
||||||
|
=> Alex Alex - Something Lossless 2023-05-20 21:29:16 UTC
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭────────────── Remove Item from Blocklist ───────────────╮
|
||||||
|
│ Do you want to remove this item from your blocklist: │
|
||||||
|
│ Alex - Something? │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│╭────────────────────────────╮╭───────────────────────────╮│
|
||||||
|
││ Yes ││ No ││
|
||||||
|
│╰────────────────────────────╯╰───────────────────────────╯│
|
||||||
|
╰───────────────────────────────────────────────────────────╯
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/blocklist/blocklist_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/blocklist/blocklist_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
Loading ...
|
||||||
@@ -23,9 +23,11 @@ mod tests {
|
|||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(ActiveLidarrBlock::Artists, 0)]
|
#[case(ActiveLidarrBlock::Artists, 0)]
|
||||||
#[case(ActiveLidarrBlock::Downloads, 1)]
|
#[case(ActiveLidarrBlock::Downloads, 1)]
|
||||||
#[case(ActiveLidarrBlock::History, 2)]
|
#[case(ActiveLidarrBlock::Blocklist, 2)]
|
||||||
#[case(ActiveLidarrBlock::RootFolders, 3)]
|
#[case(ActiveLidarrBlock::History, 3)]
|
||||||
#[case(ActiveLidarrBlock::Indexers, 4)]
|
#[case(ActiveLidarrBlock::RootFolders, 4)]
|
||||||
|
#[case(ActiveLidarrBlock::Indexers, 5)]
|
||||||
|
#[case(ActiveLidarrBlock::System, 6)]
|
||||||
fn test_lidarr_ui_renders_lidarr_tabs(
|
fn test_lidarr_ui_renders_lidarr_tabs(
|
||||||
#[case] active_lidarr_block: ActiveLidarrBlock,
|
#[case] active_lidarr_block: ActiveLidarrBlock,
|
||||||
#[case] index: usize,
|
#[case] index: usize,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ use super::{
|
|||||||
},
|
},
|
||||||
widgets::loading_block::LoadingBlock,
|
widgets::loading_block::LoadingBlock,
|
||||||
};
|
};
|
||||||
|
use crate::ui::lidarr_ui::blocklist::BlocklistUi;
|
||||||
use crate::ui::lidarr_ui::downloads::DownloadsUi;
|
use crate::ui::lidarr_ui::downloads::DownloadsUi;
|
||||||
use crate::ui::lidarr_ui::indexers::IndexersUi;
|
use crate::ui::lidarr_ui::indexers::IndexersUi;
|
||||||
use crate::ui::lidarr_ui::root_folders::RootFoldersUi;
|
use crate::ui::lidarr_ui::root_folders::RootFoldersUi;
|
||||||
@@ -39,6 +40,7 @@ use crate::{
|
|||||||
utils::convert_to_gb,
|
utils::convert_to_gb,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod blocklist;
|
||||||
mod downloads;
|
mod downloads;
|
||||||
mod history;
|
mod history;
|
||||||
mod indexers;
|
mod indexers;
|
||||||
@@ -65,6 +67,7 @@ impl DrawUi for LidarrUi {
|
|||||||
match route {
|
match route {
|
||||||
_ if LibraryUi::accepts(route) => LibraryUi::draw(f, app, content_area),
|
_ if LibraryUi::accepts(route) => LibraryUi::draw(f, app, content_area),
|
||||||
_ if DownloadsUi::accepts(route) => DownloadsUi::draw(f, app, content_area),
|
_ if DownloadsUi::accepts(route) => DownloadsUi::draw(f, app, content_area),
|
||||||
|
_ if BlocklistUi::accepts(route) => BlocklistUi::draw(f, app, content_area),
|
||||||
_ if HistoryUi::accepts(route) => HistoryUi::draw(f, app, content_area),
|
_ if HistoryUi::accepts(route) => HistoryUi::draw(f, app, content_area),
|
||||||
_ if RootFoldersUi::accepts(route) => RootFoldersUi::draw(f, app, content_area),
|
_ if RootFoldersUi::accepts(route) => RootFoldersUi::draw(f, app, content_area),
|
||||||
_ if IndexersUi::accepts(route) => IndexersUi::draw(f, app, content_area),
|
_ if IndexersUi::accepts(route) => IndexersUi::draw(f, app, content_area),
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/lidarr_ui/lidarr_ui_tests.rs
|
|||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Library │ Downloads │ History │ Root Folders │ Indexers │ System │
|
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
||||||
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags │
|
│ Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags │
|
||||||
│=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex │
|
│=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex │
|
||||||
|
|||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/lidarr_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
||||||
|
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
|
│ Artist Name ▼ Source Title Quality Date │
|
||||||
|
│=> Alex Alex - Something Lossless 2023-05-20 21:29:16 UTC │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/lidarr_ui/lidarr_ui_tests.rs
|
|||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Library │ Downloads │ History │ Root Folders │ Indexers │ System │
|
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
||||||
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ Title Percent Complete Size Output Path Indexer Download Client │
|
│ Title Percent Complete Size Output Path Indexer Download Client │
|
||||||
│=> Test download title 50% 3.30 GB /nfs/music/alex/album kickass torrents transmission │
|
│=> Test download title 50% 3.30 GB /nfs/music/alex/album kickass torrents transmission │
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/lidarr_ui/lidarr_ui_tests.rs
|
|||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Library │ Downloads │ History │ Root Folders │ Indexers │ System │
|
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
||||||
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ Source Title ▼ Event Type Quality Date │
|
│ Source Title ▼ Event Type Quality Date │
|
||||||
│=> Test source title grabbed Lossless 2023-01-01 00:00:00 UTC │
|
│=> Test source title grabbed Lossless 2023-01-01 00:00:00 UTC │
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/lidarr_ui/lidarr_ui_tests.rs
|
|||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Library │ Downloads │ History │ Root Folders │ Indexers │ System │
|
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
||||||
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ Indexer RSS Automatic Search Interactive Search Priority Tags │
|
│ Indexer RSS Automatic Search Interactive Search Priority Tags │
|
||||||
│=> Test Indexer Enabled Enabled Enabled 25 alex │
|
│=> Test Indexer Enabled Enabled Enabled 25 alex │
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ source: src/ui/lidarr_ui/lidarr_ui_tests.rs
|
|||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Library │ Downloads │ History │ Root Folders │ Indexers │ System │
|
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
||||||
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ Path Free Space Unmapped Folders │
|
│ Path Free Space Unmapped Folders │
|
||||||
│=> /nfs 204800.00 GB 0 │
|
│=> /nfs 204800.00 GB 0 │
|
||||||
|
|||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/lidarr_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
||||||
|
│╭ Tasks ───────────────────────────────────────────────────────────────────────╮╭ Queued Events ──────────────────────────────────────────────────────────────╮│
|
||||||
|
││Name Interval Last Execution Next Execution ││Trigger Status Name Queued Started Duration ││
|
||||||
|
││Backup 1 hour now 59 minutes ││manual completed Refresh Monitored 4 minutes ago 4 minutes a 00:03:03 ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
││ ││ ││
|
||||||
|
│╰────────────────────────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────────────────────╯│
|
||||||
|
│╭ Logs ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│
|
||||||
|
││2025-12-16 16:40:59 UTC|INFO|ImportListSyncService|No list items to process ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
│╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
|
||||||
|
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+1
-1
@@ -16,7 +16,7 @@ expression: output
|
|||||||
│ ││ ││ ⠀⠀⠀⠉⠻⠿⢿⡆⡾⠿⠟⠉⠀⠀⠀ │
|
│ ││ ││ ⠀⠀⠀⠉⠻⠿⢿⡆⡾⠿⠟⠉⠀⠀⠀ │
|
||||||
╰───────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────╯╰──────────────────╯
|
╰───────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────╯╰──────────────────╯
|
||||||
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Library │ Downloads │ History │ Root Folders │ Indexers │ System │
|
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
||||||
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags │
|
│ Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags │
|
||||||
│=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex │
|
│=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex │
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ expression: output
|
|||||||
│ │ s search │ ││ ⠀⠀⠀⠉⠻⠿⢿⡆⡾⠿⠟⠉⠀⠀⠀ │
|
│ │ s search │ ││ ⠀⠀⠀⠉⠻⠿⢿⡆⡾⠿⠟⠉⠀⠀⠀ │
|
||||||
╰───────────────────────────────────│ f filter │─────────────────╯╰──────────────────╯
|
╰───────────────────────────────────│ f filter │─────────────────╯╰──────────────────╯
|
||||||
╭ Artists ────────────────────────│ ctrl-r refresh │─────────────────────────────────────╮
|
╭ Artists ────────────────────────│ ctrl-r refresh │─────────────────────────────────────╮
|
||||||
│ Library │ Downloads │ History │ Ro│ u update all │ │
|
│ Library │ Downloads │ Blocklist │ │ u update all │ │
|
||||||
│───────────────────────────────────│ enter details │─────────────────────────────────────│
|
│───────────────────────────────────│ enter details │─────────────────────────────────────│
|
||||||
│ Name ▼ Typ│ esc cancel filter │e Monitored Tags │
|
│ Name ▼ Typ│ esc cancel filter │e Monitored Tags │
|
||||||
│=> Alex Per│ ↑ k scroll up │0 GB 🏷 alex │
|
│=> Alex Per│ ↑ k scroll up │0 GB 🏷 alex │
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ expression: output
|
|||||||
│ ││ ││ ⠀⠀⠀⠉⠻⠿⢿⡆⡾⠿⠟⠉⠀⠀⠀ │
|
│ ││ ││ ⠀⠀⠀⠉⠻⠿⢿⡆⡾⠿⠟⠉⠀⠀⠀ │
|
||||||
╰───────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────╯╰──────────────────╯
|
╰───────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────╯╰──────────────────╯
|
||||||
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Library │ Downloads │ History │ Root Folders │ Indexers │ System │
|
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
||||||
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags │
|
│ Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags │
|
||||||
│=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex │
|
│=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex │
|
||||||
|
|||||||
Reference in New Issue
Block a user