feat: Implemented the Lidarr History tab and CLI support
This commit is contained in:
@@ -7,7 +7,9 @@ use strum::{Display, EnumIter};
|
||||
|
||||
use super::{
|
||||
HorizontallyScrollableText, Serdeable,
|
||||
servarr_models::{DiskSpace, HostConfig, QualityProfile, RootFolder, SecurityConfig, Tag},
|
||||
servarr_models::{
|
||||
DiskSpace, HostConfig, QualityProfile, QualityWrapper, RootFolder, SecurityConfig, Tag,
|
||||
},
|
||||
};
|
||||
use crate::serde_enum_from;
|
||||
|
||||
@@ -337,6 +339,78 @@ pub struct AlbumStatistics {
|
||||
|
||||
impl Eq for AlbumStatistics {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LidarrHistoryWrapper {
|
||||
pub records: Vec<LidarrHistoryItem>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LidarrHistoryData {
|
||||
pub indexer: Option<String>,
|
||||
pub release_group: Option<String>,
|
||||
pub nzb_info_url: Option<String>,
|
||||
pub download_client_name: Option<String>,
|
||||
pub download_client: Option<String>,
|
||||
pub age: Option<String>,
|
||||
pub published_date: Option<DateTime<Utc>>,
|
||||
pub message: Option<String>,
|
||||
pub reason: Option<String>,
|
||||
pub dropped_path: Option<String>,
|
||||
pub imported_path: Option<String>,
|
||||
pub source_path: Option<String>,
|
||||
pub path: Option<String>,
|
||||
pub status_messages: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Display, EnumDisplayStyle,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum LidarrHistoryEventType {
|
||||
#[default]
|
||||
Unknown,
|
||||
Grabbed,
|
||||
#[display_style(name = "Artist Folder Imported")]
|
||||
ArtistFolderImported,
|
||||
#[display_style(name = "Album Import Incomplete")]
|
||||
AlbumImportIncomplete,
|
||||
#[display_style(name = "Download Ignored")]
|
||||
DownloadIgnored,
|
||||
#[display_style(name = "Download Imported")]
|
||||
DownloadImported,
|
||||
#[display_style(name = "Download Failed")]
|
||||
DownloadFailed,
|
||||
#[display_style(name = "Track File Deleted")]
|
||||
TrackFileDeleted,
|
||||
#[display_style(name = "Track File Imported")]
|
||||
TrackFileImported,
|
||||
#[display_style(name = "Track File Renamed")]
|
||||
TrackFileRenamed,
|
||||
#[display_style(name = "Track File Retagged")]
|
||||
TrackFileRetagged,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LidarrHistoryItem {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub id: i64,
|
||||
pub source_title: HorizontallyScrollableText,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub album_id: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub artist_id: i64,
|
||||
#[serde(default)]
|
||||
pub quality: QualityWrapper,
|
||||
pub date: DateTime<Utc>,
|
||||
pub event_type: LidarrHistoryEventType,
|
||||
#[serde(default)]
|
||||
pub data: LidarrHistoryData,
|
||||
}
|
||||
|
||||
impl From<LidarrSerdeable> for Serdeable {
|
||||
fn from(value: LidarrSerdeable) -> Serdeable {
|
||||
Serdeable::Lidarr(value)
|
||||
@@ -352,6 +426,7 @@ serde_enum_from!(
|
||||
Artists(Vec<Artist>),
|
||||
DiskSpaces(Vec<DiskSpace>),
|
||||
DownloadsResponse(DownloadsResponse),
|
||||
HistoryWrapper(LidarrHistoryWrapper),
|
||||
HostConfig(HostConfig),
|
||||
MetadataProfiles(Vec<MetadataProfile>),
|
||||
QualityProfiles(Vec<QualityProfile>),
|
||||
|
||||
@@ -5,8 +5,9 @@ mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use crate::models::lidarr_models::{
|
||||
AddArtistSearchResult, Album, DownloadRecord, DownloadStatus, DownloadsResponse, Member,
|
||||
MetadataProfile, MonitorType, NewItemMonitorType, SystemStatus,
|
||||
AddArtistSearchResult, Album, DownloadRecord, DownloadStatus, DownloadsResponse,
|
||||
LidarrHistoryEventType, LidarrHistoryItem, LidarrHistoryWrapper, Member, MetadataProfile,
|
||||
MonitorType, NewItemMonitorType, SystemStatus,
|
||||
};
|
||||
use crate::models::servarr_models::{
|
||||
DiskSpace, HostConfig, QualityProfile, RootFolder, SecurityConfig, Tag,
|
||||
@@ -302,6 +303,23 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_serdeable_from_history_wrapper() {
|
||||
let history_wrapper = LidarrHistoryWrapper {
|
||||
records: vec![LidarrHistoryItem {
|
||||
id: 1,
|
||||
..LidarrHistoryItem::default()
|
||||
}],
|
||||
};
|
||||
|
||||
let lidarr_serdeable: LidarrSerdeable = history_wrapper.clone().into();
|
||||
|
||||
assert_eq!(
|
||||
lidarr_serdeable,
|
||||
LidarrSerdeable::HistoryWrapper(history_wrapper)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_serdeable_from_metadata_profiles() {
|
||||
let metadata_profiles = vec![MetadataProfile {
|
||||
@@ -488,6 +506,90 @@ mod tests {
|
||||
assert_str_eq!(DownloadStatus::Fallback.to_display_str(), "Fallback");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_history_event_type_display() {
|
||||
assert_str_eq!(LidarrHistoryEventType::Unknown.to_string(), "unknown");
|
||||
assert_str_eq!(LidarrHistoryEventType::Grabbed.to_string(), "grabbed");
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::ArtistFolderImported.to_string(),
|
||||
"artistFolderImported"
|
||||
);
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::AlbumImportIncomplete.to_string(),
|
||||
"albumImportIncomplete"
|
||||
);
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::DownloadIgnored.to_string(),
|
||||
"downloadIgnored"
|
||||
);
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::DownloadImported.to_string(),
|
||||
"downloadImported"
|
||||
);
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::DownloadFailed.to_string(),
|
||||
"downloadFailed"
|
||||
);
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::TrackFileDeleted.to_string(),
|
||||
"trackFileDeleted"
|
||||
);
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::TrackFileImported.to_string(),
|
||||
"trackFileImported"
|
||||
);
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::TrackFileRenamed.to_string(),
|
||||
"trackFileRenamed"
|
||||
);
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::TrackFileRetagged.to_string(),
|
||||
"trackFileRetagged"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_history_event_type_to_display_str() {
|
||||
assert_str_eq!(LidarrHistoryEventType::Unknown.to_display_str(), "Unknown");
|
||||
assert_str_eq!(LidarrHistoryEventType::Grabbed.to_display_str(), "Grabbed");
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::ArtistFolderImported.to_display_str(),
|
||||
"Artist Folder Imported"
|
||||
);
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::AlbumImportIncomplete.to_display_str(),
|
||||
"Album Import Incomplete"
|
||||
);
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::DownloadIgnored.to_display_str(),
|
||||
"Download Ignored"
|
||||
);
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::DownloadImported.to_display_str(),
|
||||
"Download Imported"
|
||||
);
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::DownloadFailed.to_display_str(),
|
||||
"Download Failed"
|
||||
);
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::TrackFileDeleted.to_display_str(),
|
||||
"Track File Deleted"
|
||||
);
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::TrackFileImported.to_display_str(),
|
||||
"Track File Imported"
|
||||
);
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::TrackFileRenamed.to_display_str(),
|
||||
"Track File Renamed"
|
||||
);
|
||||
assert_str_eq!(
|
||||
LidarrHistoryEventType::TrackFileRetagged.to_display_str(),
|
||||
"Track File Retagged"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_artist_search_result_deserialization() {
|
||||
let search_result_json = json!({
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use serde_json::Number;
|
||||
|
||||
use super::modals::{AddArtistModal, EditArtistModal};
|
||||
use crate::app::context_clues::HISTORY_CONTEXT_CLUES;
|
||||
use crate::app::lidarr::lidarr_context_clues::{
|
||||
ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
|
||||
};
|
||||
use crate::models::{
|
||||
BlockSelectionState, HorizontallyScrollableText, Route, TabRoute, TabState,
|
||||
lidarr_models::{AddArtistSearchResult, Album, Artist, DownloadRecord},
|
||||
lidarr_models::{AddArtistSearchResult, Album, Artist, DownloadRecord, LidarrHistoryItem},
|
||||
servarr_models::{DiskSpace, RootFolder},
|
||||
stateful_table::StatefulTable,
|
||||
};
|
||||
@@ -21,8 +22,8 @@ use {
|
||||
crate::models::stateful_table::SortOption,
|
||||
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::quality_profile_map,
|
||||
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{
|
||||
add_artist_search_result, album, artist, download_record, metadata_profile,
|
||||
metadata_profile_map, quality_profile, root_folder, tags_map,
|
||||
add_artist_search_result, album, artist, download_record, lidarr_history_item,
|
||||
metadata_profile, metadata_profile_map, quality_profile, root_folder, tags_map,
|
||||
},
|
||||
crate::network::servarr_test_utils::diskspace,
|
||||
strum::{Display, EnumString, IntoEnumIterator},
|
||||
@@ -44,6 +45,7 @@ pub struct LidarrData<'a> {
|
||||
pub disk_space_vec: Vec<DiskSpace>,
|
||||
pub downloads: StatefulTable<DownloadRecord>,
|
||||
pub edit_artist_modal: Option<EditArtistModal>,
|
||||
pub history: StatefulTable<LidarrHistoryItem>,
|
||||
pub main_tabs: TabState,
|
||||
pub metadata_profile_map: BiMap<i64, String>,
|
||||
pub prompt_confirm: bool,
|
||||
@@ -112,6 +114,7 @@ impl<'a> Default for LidarrData<'a> {
|
||||
disk_space_vec: Vec::new(),
|
||||
downloads: StatefulTable::default(),
|
||||
edit_artist_modal: None,
|
||||
history: StatefulTable::default(),
|
||||
metadata_profile_map: BiMap::new(),
|
||||
prompt_confirm: false,
|
||||
prompt_confirm_action: None,
|
||||
@@ -121,12 +124,20 @@ impl<'a> Default for LidarrData<'a> {
|
||||
start_time: DateTime::default(),
|
||||
tags_map: BiMap::new(),
|
||||
version: String::new(),
|
||||
main_tabs: TabState::new(vec![TabRoute {
|
||||
title: "Library".to_string(),
|
||||
route: ActiveLidarrBlock::Artists.into(),
|
||||
contextual_help: Some(&ARTISTS_CONTEXT_CLUES),
|
||||
config: None,
|
||||
}]),
|
||||
main_tabs: TabState::new(vec![
|
||||
TabRoute {
|
||||
title: "Library".to_string(),
|
||||
route: ActiveLidarrBlock::Artists.into(),
|
||||
contextual_help: Some(&ARTISTS_CONTEXT_CLUES),
|
||||
config: None,
|
||||
},
|
||||
TabRoute {
|
||||
title: "History".to_string(),
|
||||
route: ActiveLidarrBlock::History.into(),
|
||||
contextual_help: Some(&HISTORY_CONTEXT_CLUES),
|
||||
config: None,
|
||||
},
|
||||
]),
|
||||
artist_info_tabs: TabState::new(vec![TabRoute {
|
||||
title: "Albums".to_string(),
|
||||
route: ActiveLidarrBlock::ArtistDetails.into(),
|
||||
@@ -195,6 +206,13 @@ impl LidarrData<'_> {
|
||||
lidarr_data.artists.search = Some("artist search".into());
|
||||
lidarr_data.artists.filter = Some("artist filter".into());
|
||||
lidarr_data.downloads.set_items(vec![download_record()]);
|
||||
lidarr_data.history.set_items(vec![lidarr_history_item()]);
|
||||
lidarr_data.history.sorting(vec![SortOption {
|
||||
name: "Date",
|
||||
cmp_fn: Some(|a: &LidarrHistoryItem, b: &LidarrHistoryItem| a.date.cmp(&b.date)),
|
||||
}]);
|
||||
lidarr_data.history.search = Some("test search".into());
|
||||
lidarr_data.history.filter = Some("test filter".into());
|
||||
lidarr_data.root_folders.set_items(vec![root_folder()]);
|
||||
lidarr_data.version = "1.0.0".to_owned();
|
||||
lidarr_data.add_artist_search = Some("Test Artist".into());
|
||||
@@ -244,10 +262,17 @@ pub enum ActiveLidarrBlock {
|
||||
EditArtistToggleMonitored,
|
||||
FilterArtists,
|
||||
FilterArtistsError,
|
||||
FilterHistory,
|
||||
FilterHistoryError,
|
||||
History,
|
||||
HistoryItemDetails,
|
||||
HistorySortPrompt,
|
||||
SearchAlbums,
|
||||
SearchAlbumsError,
|
||||
SearchArtists,
|
||||
SearchArtistsError,
|
||||
SearchHistory,
|
||||
SearchHistoryError,
|
||||
UpdateAllArtistsPrompt,
|
||||
UpdateAndScanArtistPrompt,
|
||||
}
|
||||
@@ -270,6 +295,16 @@ pub static ARTIST_DETAILS_BLOCKS: [ActiveLidarrBlock; 5] = [
|
||||
ActiveLidarrBlock::UpdateAndScanArtistPrompt,
|
||||
];
|
||||
|
||||
pub static HISTORY_BLOCKS: [ActiveLidarrBlock; 7] = [
|
||||
ActiveLidarrBlock::History,
|
||||
ActiveLidarrBlock::HistoryItemDetails,
|
||||
ActiveLidarrBlock::HistorySortPrompt,
|
||||
ActiveLidarrBlock::SearchHistory,
|
||||
ActiveLidarrBlock::SearchHistoryError,
|
||||
ActiveLidarrBlock::FilterHistory,
|
||||
ActiveLidarrBlock::FilterHistoryError,
|
||||
];
|
||||
|
||||
pub static ADD_ARTIST_BLOCKS: [ActiveLidarrBlock; 12] = [
|
||||
ActiveLidarrBlock::AddArtistAlreadyInLibrary,
|
||||
ActiveLidarrBlock::AddArtistConfirmPrompt,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::app::context_clues::HISTORY_CONTEXT_CLUES;
|
||||
use crate::app::lidarr::lidarr_context_clues::{
|
||||
ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
|
||||
};
|
||||
@@ -7,7 +8,7 @@ mod tests {
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||
ADD_ARTIST_BLOCKS, ADD_ARTIST_SELECTION_BLOCKS, ARTIST_DETAILS_BLOCKS, DELETE_ALBUM_BLOCKS,
|
||||
DELETE_ALBUM_SELECTION_BLOCKS, DELETE_ARTIST_BLOCKS, DELETE_ARTIST_SELECTION_BLOCKS,
|
||||
EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS,
|
||||
EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS, HISTORY_BLOCKS,
|
||||
};
|
||||
use crate::models::{
|
||||
BlockSelectionState, Route,
|
||||
@@ -134,6 +135,7 @@ mod tests {
|
||||
assert_is_empty!(lidarr_data.disk_space_vec);
|
||||
assert_is_empty!(lidarr_data.downloads);
|
||||
assert_none!(lidarr_data.edit_artist_modal);
|
||||
assert_is_empty!(lidarr_data.history);
|
||||
assert_is_empty!(lidarr_data.metadata_profile_map);
|
||||
assert!(!lidarr_data.prompt_confirm);
|
||||
assert_none!(lidarr_data.prompt_confirm_action);
|
||||
@@ -144,7 +146,7 @@ mod tests {
|
||||
assert_is_empty!(lidarr_data.tags_map);
|
||||
assert_is_empty!(lidarr_data.version);
|
||||
|
||||
assert_eq!(lidarr_data.main_tabs.tabs.len(), 1);
|
||||
assert_eq!(lidarr_data.main_tabs.tabs.len(), 2);
|
||||
|
||||
assert_str_eq!(lidarr_data.main_tabs.tabs[0].title, "Library");
|
||||
assert_eq!(
|
||||
@@ -157,6 +159,17 @@ mod tests {
|
||||
);
|
||||
assert_none!(lidarr_data.main_tabs.tabs[0].config);
|
||||
|
||||
assert_str_eq!(lidarr_data.main_tabs.tabs[1].title, "History");
|
||||
assert_eq!(
|
||||
lidarr_data.main_tabs.tabs[1].route,
|
||||
ActiveLidarrBlock::History.into()
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
&lidarr_data.main_tabs.tabs[1].contextual_help,
|
||||
&HISTORY_CONTEXT_CLUES
|
||||
);
|
||||
assert_none!(lidarr_data.main_tabs.tabs[1].config);
|
||||
|
||||
assert_eq!(lidarr_data.artist_info_tabs.tabs.len(), 1);
|
||||
assert_str_eq!(lidarr_data.artist_info_tabs.tabs[0].title, "Albums");
|
||||
assert_eq!(
|
||||
@@ -192,6 +205,18 @@ mod tests {
|
||||
assert!(ARTIST_DETAILS_BLOCKS.contains(&ActiveLidarrBlock::UpdateAndScanArtistPrompt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_blocks_contains_expected_blocks() {
|
||||
assert_eq!(HISTORY_BLOCKS.len(), 7);
|
||||
assert!(HISTORY_BLOCKS.contains(&ActiveLidarrBlock::History));
|
||||
assert!(HISTORY_BLOCKS.contains(&ActiveLidarrBlock::HistoryItemDetails));
|
||||
assert!(HISTORY_BLOCKS.contains(&ActiveLidarrBlock::HistorySortPrompt));
|
||||
assert!(HISTORY_BLOCKS.contains(&ActiveLidarrBlock::SearchHistory));
|
||||
assert!(HISTORY_BLOCKS.contains(&ActiveLidarrBlock::SearchHistoryError));
|
||||
assert!(HISTORY_BLOCKS.contains(&ActiveLidarrBlock::FilterHistory));
|
||||
assert!(HISTORY_BLOCKS.contains(&ActiveLidarrBlock::FilterHistoryError));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_artist_blocks_contents() {
|
||||
assert_eq!(ADD_ARTIST_BLOCKS.len(), 12);
|
||||
|
||||
@@ -2,12 +2,11 @@ use super::modals::{AddSeriesModal, EditSeriesModal, SeasonDetailsModal};
|
||||
use crate::{
|
||||
app::{
|
||||
context_clues::{
|
||||
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_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,
|
||||
},
|
||||
sonarr::sonarr_context_clues::{
|
||||
HISTORY_CONTEXT_CLUES, SERIES_CONTEXT_CLUES, SERIES_DETAILS_CONTEXT_CLUES,
|
||||
SERIES_HISTORY_CONTEXT_CLUES,
|
||||
SERIES_CONTEXT_CLUES, SERIES_DETAILS_CONTEXT_CLUES, SERIES_HISTORY_CONTEXT_CLUES,
|
||||
},
|
||||
},
|
||||
models::{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod sonarr_data_tests {
|
||||
use crate::app::context_clues::HISTORY_CONTEXT_CLUES;
|
||||
use crate::app::sonarr::sonarr_context_clues::SERIES_HISTORY_CONTEXT_CLUES;
|
||||
use crate::models::sonarr_models::{Season, SonarrHistoryItem};
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
@@ -10,9 +11,7 @@ mod tests {
|
||||
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
||||
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
||||
},
|
||||
sonarr::sonarr_context_clues::{
|
||||
HISTORY_CONTEXT_CLUES, SERIES_CONTEXT_CLUES, SERIES_DETAILS_CONTEXT_CLUES,
|
||||
},
|
||||
sonarr::sonarr_context_clues::{SERIES_CONTEXT_CLUES, SERIES_DETAILS_CONTEXT_CLUES},
|
||||
},
|
||||
models::{
|
||||
BlockSelectionState, Route,
|
||||
|
||||
Reference in New Issue
Block a user