feat: Blocklist support in Lidarr in both the CLI and TUI

This commit is contained in:
2026-01-19 16:13:11 -07:00
parent eff1a901eb
commit 89f5ff6bc7
48 changed files with 2211 additions and 66 deletions
+23
View File
@@ -499,6 +499,28 @@ pub struct LidarrReleaseDownloadBody {
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)]
#[serde(rename_all = "camelCase")]
pub struct TrackFile {
@@ -574,6 +596,7 @@ serde_enum_from!(
Album(Album),
Artist(Artist),
Artists(Vec<Artist>),
BlocklistResponse(BlocklistResponse),
DiskSpaces(Vec<DiskSpace>),
DownloadsResponse(DownloadsResponse),
LidarrHistoryWrapper(LidarrHistoryWrapper),
+21 -4
View File
@@ -5,10 +5,10 @@ mod tests {
use serde_json::json;
use crate::models::lidarr_models::{
AddArtistSearchResult, Album, AudioTags, DownloadRecord, DownloadStatus, DownloadsResponse,
LidarrHistoryEventType, LidarrHistoryItem, LidarrHistoryWrapper, LidarrRelease, LidarrTask,
MediaInfo, Member, MetadataProfile, MonitorType, NewItemMonitorType, SystemStatus, Track,
TrackFile,
AddArtistSearchResult, Album, AudioTags, BlocklistItem, BlocklistResponse, DownloadRecord,
DownloadStatus, DownloadsResponse, LidarrHistoryEventType, LidarrHistoryItem,
LidarrHistoryWrapper, LidarrRelease, LidarrTask, MediaInfo, Member, MetadataProfile,
MonitorType, NewItemMonitorType, SystemStatus, Track, TrackFile,
};
use crate::models::servarr_models::{
DiskSpace, HostConfig, Indexer, IndexerSettings, IndexerTestResult, Log, LogResponse,
@@ -276,6 +276,23 @@ mod tests {
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]
fn test_lidarr_serdeable_from_disk_spaces() {
let disk_spaces = vec![DiskSpace {
+26 -2
View File
@@ -2,14 +2,14 @@ use serde_json::Number;
use super::modals::{AddArtistModal, AddRootFolderModal, AlbumDetailsModal, EditArtistModal};
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,
};
use crate::app::lidarr::lidarr_context_clues::{
ARTIST_DETAILS_CONTEXT_CLUES, ARTIST_HISTORY_CONTEXT_CLUES, ARTISTS_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_models::{IndexerSettings, QueueEvent};
use crate::models::stateful_list::StatefulList;
@@ -30,6 +30,7 @@ use {
super::modals::TrackDetailsModal,
crate::models::lidarr_models::{MonitorType, NewItemMonitorType},
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::quality_profile_map,
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_info_tabs: TabState,
pub artists: StatefulTable<Artist>,
pub blocklist: StatefulTable<BlocklistItem>,
pub delete_files: bool,
pub discography_releases: StatefulTable<LidarrRelease>,
pub disk_space_vec: Vec<DiskSpace>,
@@ -149,6 +151,7 @@ impl<'a> Default for LidarrData<'a> {
album_details_modal: None,
artist_history: StatefulTable::default(),
artists: StatefulTable::default(),
blocklist: StatefulTable::default(),
delete_files: false,
discography_releases: StatefulTable::default(),
disk_space_vec: Vec::new(),
@@ -187,6 +190,12 @@ impl<'a> Default for LidarrData<'a> {
contextual_help: Some(&DOWNLOADS_CONTEXT_CLUES),
config: None,
},
TabRoute {
title: "Blocklist".to_string(),
route: ActiveLidarrBlock::Blocklist.into(),
contextual_help: Some(&BLOCKLIST_CONTEXT_CLUES),
config: None,
},
TabRoute {
title: "History".to_string(),
route: ActiveLidarrBlock::History.into(),
@@ -377,6 +386,8 @@ impl LidarrData<'_> {
}]);
lidarr_data.artists.search = Some("artist search".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.history.set_items(vec![lidarr_history_item()]);
lidarr_data.history.sorting(vec![SortOption {
@@ -444,6 +455,11 @@ pub enum ActiveLidarrBlock {
AllIndexerSettingsPrompt,
AutomaticallySearchAlbumPrompt,
AutomaticallySearchArtistPrompt,
Blocklist,
BlocklistItemDetails,
DeleteBlocklistItemPrompt,
BlocklistClearAllItemsPrompt,
BlocklistSortPrompt,
DeleteAlbumPrompt,
DeleteAlbumConfirmPrompt,
DeleteAlbumToggleDeleteFile,
@@ -579,6 +595,14 @@ pub static ALBUM_DETAILS_BLOCKS: [ActiveLidarrBlock; 15] = [
ActiveLidarrBlock::DeleteTrackFilePrompt,
];
pub static BLOCKLIST_BLOCKS: [ActiveLidarrBlock; 5] = [
ActiveLidarrBlock::Blocklist,
ActiveLidarrBlock::BlocklistItemDetails,
ActiveLidarrBlock::DeleteBlocklistItemPrompt,
ActiveLidarrBlock::BlocklistClearAllItemsPrompt,
ActiveLidarrBlock::BlocklistSortPrompt,
];
pub static DOWNLOADS_BLOCKS: [ActiveLidarrBlock; 3] = [
ActiveLidarrBlock::Downloads,
ActiveLidarrBlock::DeleteDownloadPrompt,
@@ -1,8 +1,8 @@
#[cfg(test)]
mod tests {
use crate::app::context_clues::{
DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES,
INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
};
use crate::app::lidarr::lidarr_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::servarr_data::lidarr::lidarr_data::{
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,
EDIT_ARTIST_SELECTION_BLOCKS, EDIT_INDEXER_BLOCKS, EDIT_INDEXER_NZB_SELECTION_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_is_empty!(lidarr_data.artists);
assert_is_empty!(lidarr_data.artist_history);
assert_is_empty!(lidarr_data.blocklist);
assert!(!lidarr_data.delete_files);
assert_is_empty!(lidarr_data.disk_space_vec);
assert_is_empty!(lidarr_data.downloads);
@@ -171,7 +172,7 @@ mod tests {
assert_is_empty!(lidarr_data.updates);
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_eq!(
@@ -195,50 +196,61 @@ mod tests {
);
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!(
lidarr_data.main_tabs.tabs[2].route,
ActiveLidarrBlock::History.into()
ActiveLidarrBlock::Blocklist.into()
);
assert_some_eq_x!(
&lidarr_data.main_tabs.tabs[2].contextual_help,
&HISTORY_CONTEXT_CLUES
&BLOCKLIST_CONTEXT_CLUES
);
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!(
lidarr_data.main_tabs.tabs[3].route,
ActiveLidarrBlock::RootFolders.into()
ActiveLidarrBlock::History.into()
);
assert_some_eq_x!(
&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_str_eq!(lidarr_data.main_tabs.tabs[4].title, "Indexers");
assert_str_eq!(lidarr_data.main_tabs.tabs[4].title, "Root Folders");
assert_eq!(
lidarr_data.main_tabs.tabs[4].route,
ActiveLidarrBlock::Indexers.into()
ActiveLidarrBlock::RootFolders.into()
);
assert_some_eq_x!(
&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_str_eq!(lidarr_data.main_tabs.tabs[5].title, "System");
assert_str_eq!(lidarr_data.main_tabs.tabs[5].title, "Indexers");
assert_eq!(
lidarr_data.main_tabs.tabs[5].route,
ActiveLidarrBlock::System.into()
ActiveLidarrBlock::Indexers.into()
);
assert_some_eq_x!(
&lidarr_data.main_tabs.tabs[5].contextual_help,
&SYSTEM_CONTEXT_CLUES
&INDEXERS_CONTEXT_CLUES
);
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_str_eq!(lidarr_data.artist_info_tabs.tabs[0].title, "Albums");
assert_eq!(
@@ -326,6 +338,16 @@ mod tests {
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]
fn test_downloads_blocks_contains_expected_blocks() {
assert_eq!(DOWNLOADS_BLOCKS.len(), 3);